diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java index 632253eed94..7f96d179016 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java @@ -1634,6 +1634,6 @@ public Supplier getCollectionFactory() { @Override public void setCollectionFactory(Supplier cf) { - this.collectionFactory = cf; + this.collectionFactory = cf != null ? cf : DefaultCollectionFactory::new; } } diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactory.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactory.java new file mode 100644 index 00000000000..f25ba223ab1 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactory.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.impl; + +import java.util.Objects; + +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.RDFStarTripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.LearningQueryOptimizerPipeline; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.LearningRdfStarTripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.LearningTripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; + +/** + * Evaluation strategy factory that injects a learned join optimizer. + * + *

+ * Example enablement: + * + *

{@code
+ * LearnedJoinConfig config = new LearnedJoinConfig(
+ * 		LearnedJoinConfig.DEFAULT_DP_THRESHOLD,
+ * 		true);
+ * LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(new MemoryJoinStats(), null,
+ * 		config);
+ * NativeStore store = new NativeStore(dataDir);
+ * store.setEvaluationStrategyFactory(factory);
+ * }
+ */ +public class LearningEvaluationStrategyFactory extends DefaultEvaluationStrategyFactory { + + private final JoinStatsProvider statsProvider; + private final EvaluationStatistics optimizerStatisticsOverride; + private final LearnedJoinConfig joinConfig; + + public LearningEvaluationStrategyFactory() { + this(new MemoryJoinStats(), null, new LearnedJoinConfig()); + } + + public LearningEvaluationStrategyFactory(FederatedServiceResolver resolver) { + this(new MemoryJoinStats(), null, new LearnedJoinConfig()); + setFederatedServiceResolver(resolver); + } + + public LearningEvaluationStrategyFactory(EvaluationStatistics optimizerStatisticsOverride) { + this(new MemoryJoinStats(), optimizerStatisticsOverride, new LearnedJoinConfig()); + } + + public LearningEvaluationStrategyFactory(JoinStatsProvider statsProvider) { + this(statsProvider, null, new LearnedJoinConfig()); + } + + public LearningEvaluationStrategyFactory(JoinStatsProvider statsProvider, + EvaluationStatistics optimizerStatisticsOverride) { + this(statsProvider, optimizerStatisticsOverride, new LearnedJoinConfig()); + } + + public LearningEvaluationStrategyFactory(LearnedJoinConfig joinConfig) { + this(new MemoryJoinStats(), null, joinConfig); + } + + public LearningEvaluationStrategyFactory(JoinStatsProvider statsProvider, LearnedJoinConfig joinConfig) { + this(statsProvider, null, joinConfig); + } + + public LearningEvaluationStrategyFactory(JoinStatsProvider statsProvider, + EvaluationStatistics optimizerStatisticsOverride, LearnedJoinConfig joinConfig) { + this.statsProvider = Objects.requireNonNull(statsProvider, "statsProvider"); + this.optimizerStatisticsOverride = optimizerStatisticsOverride; + this.joinConfig = Objects.requireNonNull(joinConfig, "joinConfig"); + } + + public JoinStatsProvider getStatsProvider() { + return statsProvider; + } + + public LearnedJoinConfig getJoinConfig() { + return joinConfig; + } + + @Override + public EvaluationStrategy createEvaluationStrategy(Dataset dataset, TripleSource tripleSource, + EvaluationStatistics evaluationStatistics) { + TripleSource learningTripleSource = tripleSource instanceof RDFStarTripleSource + ? new LearningRdfStarTripleSource((RDFStarTripleSource) tripleSource, statsProvider) + : new LearningTripleSource(tripleSource, statsProvider); + EvaluationStrategy strategy = super.createEvaluationStrategy(dataset, learningTripleSource, + evaluationStatistics); + EvaluationStatistics optimizerStatistics = optimizerStatisticsOverride != null + ? optimizerStatisticsOverride + : evaluationStatistics; + strategy.setOptimizerPipeline( + new LearningQueryOptimizerPipeline(strategy, learningTripleSource, optimizerStatistics, + statsProvider, joinConfig)); + return strategy; + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/JoinStatsProvider.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/JoinStatsProvider.java new file mode 100644 index 00000000000..d61c1368d10 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/JoinStatsProvider.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +/** + * Collects and supplies statistics about triple pattern evaluations. + */ +public interface JoinStatsProvider { + + void reset(); + + void recordCall(PatternKey key); + + void recordResults(PatternKey key, long resultCount); + + /** + * Seeds statistics for the given key. Implementations may also invalidate or refresh existing entries if the + * supplied default cardinality has drifted significantly from the stored baseline. + */ + void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls); + + double getAverageResults(PatternKey key); + + boolean hasStats(PatternKey key); + + long getTotalCalls(); + + /** + * Records that statements have been added to the store. Implementations may use this to invalidate statistics when + * a write threshold is exceeded in a time window. + */ + default void recordStatementsAdded(long statementCount) { + // no-op by default + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearnedQueryJoinOptimizer.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearnedQueryJoinOptimizer.java new file mode 100644 index 00000000000..cf5933f7a48 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearnedQueryJoinOptimizer.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.Dataset; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.BindJoinCostModel; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.DpLeftDeepBindJoinOrderPlanner; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.GreedyBindJoinOrderPlanner; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.HybridBindJoinOrderPlanner; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.JoinOrderPlanner; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedBindJoinCostModel; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; +import org.eclipse.rdf4j.query.algebra.helpers.TupleExprs; +import org.eclipse.rdf4j.query.algebra.helpers.collectors.StatementPatternCollector; + +/** + * Join optimizer that uses learned fanout statistics to estimate costs. + */ +public class LearnedQueryJoinOptimizer extends QueryJoinOptimizer { + + private static final long DEFAULT_PRIOR_CALLS = 2; + + private final JoinStatsProvider statsProvider; + private final JoinOrderPlanner joinPlanner; + private final LearnedJoinConfig config; + + public LearnedQueryJoinOptimizer(EvaluationStatistics statistics, TripleSource tripleSource, + JoinStatsProvider statsProvider) { + this(statistics, false, tripleSource, statsProvider, new LearnedJoinConfig()); + } + + public LearnedQueryJoinOptimizer(EvaluationStatistics statistics, TripleSource tripleSource, + JoinStatsProvider statsProvider, LearnedJoinConfig config) { + this(statistics, false, tripleSource, statsProvider, config); + } + + public LearnedQueryJoinOptimizer(EvaluationStatistics statistics, boolean trackResultSize, + TripleSource tripleSource, JoinStatsProvider statsProvider) { + this(statistics, trackResultSize, tripleSource, statsProvider, new LearnedJoinConfig()); + } + + public LearnedQueryJoinOptimizer(EvaluationStatistics statistics, boolean trackResultSize, + TripleSource tripleSource, JoinStatsProvider statsProvider, LearnedJoinConfig config) { + super(statistics, trackResultSize, tripleSource); + this.statsProvider = Objects.requireNonNull(statsProvider, "statsProvider"); + this.config = Objects.requireNonNull(config, "config"); + Objects.requireNonNull(tripleSource, "tripleSource"); + BindJoinCostModel costModel = new LearnedBindJoinCostModel(statistics, statsProvider); + JoinOrderPlanner greedy = new GreedyBindJoinOrderPlanner(costModel); + JoinOrderPlanner dp = new DpLeftDeepBindJoinOrderPlanner(costModel); + this.joinPlanner = new HybridBindJoinOrderPlanner(config, greedy, dp); + } + + @Override + public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings) { + tupleExpr.visit(new LearnedJoinVisitor(dataset, bindings)); + } + + protected class LearnedJoinVisitor extends JoinVisitor { + + private final Dataset dataset; + private final BindingSet bindings; + private Deque plannedOrder; + + private LearnedJoinVisitor(Dataset dataset, BindingSet bindings) { + this.dataset = dataset; + this.bindings = bindings; + } + + @Override + public void meet(Join node) { + Deque previousPlan = plannedOrder; + try { + List joinArgs = getJoinArgs(node, new ArrayList<>()); + List orderedExtensions = getExtensionTupleExprs(joinArgs); + joinArgs.removeAll(orderedExtensions); + List subSelects = getSubSelects(joinArgs); + List orderedSubselects = reorderSubselects(subSelects); + joinArgs.removeAll(orderedSubselects); + if (joinArgs.isEmpty()) { + plannedOrder = null; + } else if (!hasLearnedStats(joinArgs)) { + plannedOrder = null; + } else { + Set initiallyBoundVars = determineInitiallyBoundVars(joinArgs); + List planned = joinPlanner.order(joinArgs, initiallyBoundVars); + if (isConnectedPlan(planned, initiallyBoundVars)) { + plannedOrder = new ArrayDeque<>(planned); + } else { + plannedOrder = null; + } + } + super.meet(node); + } finally { + plannedOrder = previousPlan; + } + } + + @Override + public void meet(StatementPattern node) { + double estimate = estimateCardinality(node); + node.setResultSizeEstimate(estimate); + } + + @Override + protected double getTupleExprCost(TupleExpr tupleExpr, Map cardinalityMap, + Map> varsMap, Map varFreqMap) { + if (tupleExpr instanceof StatementPattern) { + StatementPattern statementPattern = (StatementPattern) tupleExpr; + double estimate = estimateCardinality(statementPattern); + statementPattern.setCardinality(estimate); + statementPattern.setResultSizeEstimate(estimate); + } + return super.getTupleExprCost(tupleExpr, cardinalityMap, varsMap, varFreqMap); + } + + @Override + protected TupleExpr selectNextTupleExpr(List expressions, Map cardinalityMap, + Map> varsMap, Map varFreqMap) { + if (plannedOrder != null && !plannedOrder.isEmpty()) { + TupleExpr planned = nextPlanned(expressions); + if (planned != null) { + if (planned.getCostEstimate() < 0) { + planned.setCostEstimate(getTupleExprCost(planned, cardinalityMap, varsMap, varFreqMap)); + } + return planned; + } + } + return super.selectNextTupleExpr(expressions, cardinalityMap, varsMap, varFreqMap); + } + + private TupleExpr nextPlanned(List expressions) { + while (!plannedOrder.isEmpty()) { + TupleExpr candidate = plannedOrder.removeFirst(); + if (expressions.contains(candidate)) { + return candidate; + } + } + return null; + } + + private Set determineInitiallyBoundVars(List joinArgs) { + Set candidates = new HashSet<>(); + for (TupleExpr expr : joinArgs) { + candidates.addAll(expr.getBindingNames()); + } + Set bound = new HashSet<>(); + for (String name : candidates) { + if (name.startsWith("_const_")) { + continue; + } + if (getUnboundVars(List.of(Var.of(name))).isEmpty()) { + bound.add(name); + } + } + return bound; + } + + private boolean isConnectedPlan(List plan, Set initiallyBoundVars) { + if (plan.isEmpty()) { + return true; + } + Set bound = new HashSet<>(); + for (TupleExpr expr : plan) { + Set names = filteredBindingNames(expr); + if (!bound.isEmpty() && disjoint(bound, names)) { + return false; + } + bound.addAll(names); + } + return true; + } + + private Set filteredBindingNames(TupleExpr expr) { + Set names = new HashSet<>(expr.getBindingNames()); + names.removeIf(name -> name.startsWith("_const_")); + return names; + } + + private boolean disjoint(Set left, Set right) { + for (String name : left) { + if (right.contains(name)) { + return false; + } + } + return true; + } + + private List getExtensionTupleExprs(List expressions) { + if (expressions.isEmpty()) { + return List.of(); + } + + List extensions = List.of(); + for (TupleExpr expr : expressions) { + if (TupleExprs.containsExtension(expr)) { + if (extensions.isEmpty()) { + extensions = List.of(expr); + } else { + if (extensions.size() == 1) { + extensions = new ArrayList<>(extensions); + } + extensions.add(expr); + } + } + } + return extensions; + } + + private boolean hasLearnedStats(List expressions) { + for (TupleExpr expr : expressions) { + if (expr instanceof StatementPattern) { + if (statsProvider.hasStats(buildKey((StatementPattern) expr))) { + return true; + } + continue; + } + for (StatementPattern pattern : StatementPatternCollector.process(expr)) { + if (statsProvider.hasStats(buildKey(pattern))) { + return true; + } + } + } + return false; + } + + private double estimateCardinality(StatementPattern node) { + PatternKey key = buildKey(node); + double defaultEstimate = statistics.getCardinality(node); + if (!statsProvider.hasStats(key)) { + return defaultEstimate; + } + statsProvider.seedIfAbsent(key, defaultEstimate, DEFAULT_PRIOR_CALLS); + double estimate = statsProvider.getAverageResults(key); + if (estimate <= 0.0d) { + estimate = defaultEstimate; + } + return estimate; + } + + private PatternKey buildKey(StatementPattern node) { + int mask = 0; + if (isBound(node.getSubjectVar())) { + mask |= PatternKey.SUBJECT_BOUND; + } + if (isBound(node.getPredicateVar())) { + mask |= PatternKey.PREDICATE_BOUND; + } + if (isBound(node.getObjectVar())) { + mask |= PatternKey.OBJECT_BOUND; + } + Var predVar = node.getPredicateVar(); + IRI predicateKey = null; + if (predVar != null && predVar.hasValue() && predVar.getValue() instanceof IRI) { + predicateKey = (IRI) predVar.getValue(); + } + return new PatternKey(predicateKey, mask); + } + + private boolean isBound(Var var) { + if (var == null) { + return false; + } + List unbound = getUnboundVars(List.of(var)); + return unbound.isEmpty(); + } + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningQueryOptimizerPipeline.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningQueryOptimizerPipeline.java new file mode 100644 index 00000000000..f7509dd73b8 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningQueryOptimizerPipeline.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer; +import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizerPipeline; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; + +/** + * Standard optimizer pipeline with a learned join optimizer. + */ +public class LearningQueryOptimizerPipeline implements QueryOptimizerPipeline { + + private static boolean assertsEnabled = false; + + static { + // noinspection AssertWithSideEffects + assert assertsEnabled = true; + } + + private final EvaluationStatistics evaluationStatistics; + private final TripleSource tripleSource; + private final EvaluationStrategy strategy; + private final JoinStatsProvider statsProvider; + private final LearnedJoinConfig joinConfig; + + public LearningQueryOptimizerPipeline(EvaluationStrategy strategy, TripleSource tripleSource, + EvaluationStatistics evaluationStatistics, JoinStatsProvider statsProvider) { + this(strategy, tripleSource, evaluationStatistics, statsProvider, new LearnedJoinConfig()); + } + + public LearningQueryOptimizerPipeline(EvaluationStrategy strategy, TripleSource tripleSource, + EvaluationStatistics evaluationStatistics, JoinStatsProvider statsProvider, LearnedJoinConfig joinConfig) { + this.strategy = strategy; + this.tripleSource = tripleSource; + this.evaluationStatistics = evaluationStatistics; + this.statsProvider = statsProvider; + this.joinConfig = Objects.requireNonNull(joinConfig, "joinConfig"); + } + + @Override + public Iterable getOptimizers() { + List optimizers = List.of( + StandardQueryOptimizerPipeline.BINDING_ASSIGNER, + StandardQueryOptimizerPipeline.BINDING_SET_ASSIGNMENT_INLINER, + new ConstantOptimizer(strategy), + new RegexAsStringFunctionOptimizer(tripleSource.getValueFactory()), + StandardQueryOptimizerPipeline.COMPARE_OPTIMIZER, + StandardQueryOptimizerPipeline.CONJUNCTIVE_CONSTRAINT_SPLITTER, + StandardQueryOptimizerPipeline.DISJUNCTIVE_CONSTRAINT_OPTIMIZER, + StandardQueryOptimizerPipeline.SAME_TERM_FILTER_OPTIMIZER, + StandardQueryOptimizerPipeline.UNION_SCOPE_CHANGE_OPTIMIZER, + StandardQueryOptimizerPipeline.QUERY_MODEL_NORMALIZER, + StandardQueryOptimizerPipeline.PROJECTION_REMOVAL_OPTIMIZER, + new LearnedQueryJoinOptimizer(evaluationStatistics, strategy.isTrackResultSize(), tripleSource, + statsProvider, joinConfig), + StandardQueryOptimizerPipeline.ITERATIVE_EVALUATION_OPTIMIZER, + StandardQueryOptimizerPipeline.FILTER_OPTIMIZER, + StandardQueryOptimizerPipeline.ORDER_LIMIT_OPTIMIZER + ); + + if (assertsEnabled) { + List optimizersWithReferenceChecker = new ArrayList<>(); + optimizersWithReferenceChecker.add(new ParentReferenceChecker(null)); + for (QueryOptimizer optimizer : optimizers) { + optimizersWithReferenceChecker.add(optimizer); + optimizersWithReferenceChecker.add(new ParentReferenceChecker(optimizer)); + } + optimizers = optimizersWithReferenceChecker; + } + + return optimizers; + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningRdfStarTripleSource.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningRdfStarTripleSource.java new file mode 100644 index 00000000000..a1e0aeba02a --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningRdfStarTripleSource.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Triple; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.RDFStarTripleSource; + +/** + * RDF-star aware wrapper that records call counts and result sizes. + */ +public class LearningRdfStarTripleSource extends LearningTripleSource implements RDFStarTripleSource { + + private final RDFStarTripleSource rdfStarDelegate; + + public LearningRdfStarTripleSource(RDFStarTripleSource delegate, JoinStatsProvider statsProvider) { + super(delegate, statsProvider); + this.rdfStarDelegate = delegate; + } + + @Override + public CloseableIteration getRdfStarTriples(Resource subj, IRI pred, Value obj) + throws QueryEvaluationException { + PatternKey key = buildKey(subj, pred, obj); + CloseableIteration base = rdfStarDelegate.getRdfStarTriples(subj, pred, obj); + return new CountingIteration<>(base, statsProvider, key); + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSource.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSource.java new file mode 100644 index 00000000000..3f64885df4f --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSource.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.AbstractCloseableIteration; +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; + +/** + * TripleSource wrapper that records call counts and result sizes. + */ +public class LearningTripleSource implements TripleSource { + + protected final TripleSource delegate; + protected final JoinStatsProvider statsProvider; + + public LearningTripleSource(TripleSource delegate, JoinStatsProvider statsProvider) { + this.delegate = Objects.requireNonNull(delegate, "delegate"); + this.statsProvider = Objects.requireNonNull(statsProvider, "statsProvider"); + } + + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) { + PatternKey key = buildKey(subj, pred, obj); + CloseableIteration base = delegate.getStatements(subj, pred, obj, contexts); + return new CountingIteration<>(base, statsProvider, key); + } + + @Override + public CloseableIteration getStatements(StatementOrder order, Resource subj, IRI pred, + Value obj, Resource... contexts) { + PatternKey key = buildKey(subj, pred, obj); + CloseableIteration base = delegate.getStatements(order, subj, pred, obj, contexts); + return new CountingIteration<>(base, statsProvider, key); + } + + @Override + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { + return delegate.getSupportedOrders(subj, pred, obj, contexts); + } + + @Override + public Comparator getComparator() { + return delegate.getComparator(); + } + + @Override + public ValueFactory getValueFactory() { + return delegate.getValueFactory(); + } + + protected static PatternKey buildKey(Resource subj, IRI pred, Value obj) { + int mask = 0; + if (subj != null) { + mask |= PatternKey.SUBJECT_BOUND; + } + if (pred != null) { + mask |= PatternKey.PREDICATE_BOUND; + } + if (obj != null) { + mask |= PatternKey.OBJECT_BOUND; + } + return new PatternKey(pred, mask); + } + + protected static final class CountingIteration extends AbstractCloseableIteration { + + private final CloseableIteration delegate; + private final JoinStatsProvider statsProvider; + private final PatternKey key; + private long count; + private boolean recordedCall; + private boolean sawHasNextTrue; + + protected CountingIteration(CloseableIteration delegate, JoinStatsProvider statsProvider, + PatternKey key) { + this.delegate = delegate; + this.statsProvider = statsProvider; + this.key = key; + } + + @Override + public boolean hasNext() { + if (isClosed()) { + return false; + } else if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } + boolean result = delegate.hasNext(); + if (result) { + sawHasNextTrue = true; + } + if (!result) { + close(); + } + return result; + } + + @Override + public T next() { + if (isClosed()) { + throw new NoSuchElementException("The iteration has been closed."); + } else if (Thread.currentThread().isInterrupted()) { + close(); + throw new NoSuchElementException("The iteration has been interrupted."); + } + try { + T statement = delegate.next(); + if (!recordedCall) { + statsProvider.recordCall(key); + recordedCall = true; + } + count++; + return statement; + } catch (NoSuchElementException e) { + close(); + throw e; + } + } + + @Override + public void remove() { + if (isClosed()) { + throw new IllegalStateException("The iteration has been closed."); + } else if (Thread.currentThread().isInterrupted()) { + close(); + throw new IllegalStateException("The iteration has been interrupted."); + } + try { + delegate.remove(); + } catch (IllegalStateException e) { + close(); + throw e; + } + } + + @Override + protected void handleClose() { + try { + delegate.close(); + } finally { + if (!recordedCall) { + if (sawHasNextTrue) { + return; + } + statsProvider.recordCall(key); + recordedCall = true; + } + statsProvider.recordResults(key, count); + } + } + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStats.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStats.java new file mode 100644 index 00000000000..3200b14b347 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStats.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import java.time.Clock; +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +/** + * In-memory statistics store for join pattern fanout metrics. + */ +public class MemoryJoinStats implements JoinStatsProvider { + + public static final Duration DEFAULT_INVALIDATION_WINDOW = Duration.ofMinutes(10); + public static final long DEFAULT_INVALIDATION_STATEMENT_THRESHOLD = 100_000L; + public static final double DEFAULT_BASELINE_DRIFT_RATIO = 0.5d; + + public static final class InvalidationSettings { + private final Duration window; + private final long statementThreshold; + private final double baselineDriftRatio; + + private InvalidationSettings(Duration window, long statementThreshold, double baselineDriftRatio) { + this.window = window; + this.statementThreshold = statementThreshold; + this.baselineDriftRatio = baselineDriftRatio; + } + + public static InvalidationSettings disabled() { + return new InvalidationSettings(Duration.ZERO, 0, 0.0d); + } + + public static InvalidationSettings of(Duration window, long statementThreshold) { + return of(window, statementThreshold, DEFAULT_BASELINE_DRIFT_RATIO); + } + + public static InvalidationSettings of(Duration window, long statementThreshold, double baselineDriftRatio) { + Objects.requireNonNull(window, "window"); + if (window.isNegative() || window.isZero()) { + throw new IllegalArgumentException("window must be positive"); + } + if (statementThreshold <= 0) { + throw new IllegalArgumentException("statementThreshold must be positive"); + } + if (baselineDriftRatio < 0.0d) { + throw new IllegalArgumentException("baselineDriftRatio must be >= 0"); + } + return new InvalidationSettings(window, statementThreshold, baselineDriftRatio); + } + + public Duration getWindow() { + return window; + } + + public long getStatementThreshold() { + return statementThreshold; + } + + public double getBaselineDriftRatio() { + return baselineDriftRatio; + } + + private boolean enabled() { + return statementThreshold > 0 && !window.isZero(); + } + } + + private static final class Stats { + private final LongAdder calls = new LongAdder(); + private final LongAdder results = new LongAdder(); + private final long priorCalls; + private final double priorResults; + private final double baselineCardinality; + + private Stats(long priorCalls, double priorResults, double baselineCardinality) { + this.priorCalls = priorCalls; + this.priorResults = priorResults; + this.baselineCardinality = baselineCardinality; + } + + private double averageResults() { + long actualCalls = calls.sum(); + long totalCalls = actualCalls + priorCalls; + if (totalCalls == 0) { + return 0.0d; + } + return (results.sum() + priorResults) / totalCalls; + } + + private long actualCalls() { + return calls.sum(); + } + + private double baselineCardinality() { + return baselineCardinality; + } + + private Stats withBaseline(double newBaseline) { + Stats updated = new Stats(priorCalls, priorResults, newBaseline); + updated.calls.add(calls.sum()); + updated.results.add(results.sum()); + return updated; + } + } + + private final Map stats = new ConcurrentHashMap<>(); + private final InvalidationSettings invalidationSettings; + private final Clock clock; + private final Object invalidationLock = new Object(); + private final AtomicLong windowStartMillis; + private final LongAdder statementsAddedInWindow = new LongAdder(); + + public MemoryJoinStats() { + this(InvalidationSettings.of(DEFAULT_INVALIDATION_WINDOW, DEFAULT_INVALIDATION_STATEMENT_THRESHOLD), + Clock.systemUTC()); + } + + public MemoryJoinStats(InvalidationSettings invalidationSettings) { + this(invalidationSettings, Clock.systemUTC()); + } + + public MemoryJoinStats(InvalidationSettings invalidationSettings, Clock clock) { + this.invalidationSettings = Objects.requireNonNull(invalidationSettings, "invalidationSettings"); + this.clock = Objects.requireNonNull(clock, "clock"); + long start = invalidationSettings.enabled() ? clock.millis() : 0L; + this.windowStartMillis = new AtomicLong(start); + } + + @Override + public void reset() { + stats.clear(); + } + + @Override + public void recordCall(PatternKey key) { + stats.computeIfAbsent(key, ignored -> new Stats(0, 0, 0.0d)).calls.increment(); + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + if (resultCount < 0) { + resultCount = 0; + } + stats.computeIfAbsent(key, ignored -> new Stats(0, 0, 0.0d)).results.add(resultCount); + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + long seedCalls = Math.max(0, priorCalls); + double priorResults = Math.max(0.0d, defaultCardinality) * seedCalls; + stats.compute(key, (ignored, existing) -> { + if (existing == null) { + return new Stats(seedCalls, priorResults, defaultCardinality); + } + if (baselineDrifted(existing, defaultCardinality)) { + return new Stats(seedCalls, priorResults, defaultCardinality); + } + if (shouldInitializeBaseline(existing, defaultCardinality)) { + return existing.withBaseline(defaultCardinality); + } + return existing; + }); + } + + @Override + public double getAverageResults(PatternKey key) { + Stats entry = stats.get(key); + return entry == null ? 0.0d : entry.averageResults(); + } + + @Override + public boolean hasStats(PatternKey key) { + return stats.containsKey(key); + } + + @Override + public long getTotalCalls() { + long total = 0; + for (Stats entry : stats.values()) { + total += entry.actualCalls(); + } + return total; + } + + @Override + public void recordStatementsAdded(long statementCount) { + if (!invalidationSettings.enabled() || statementCount <= 0) { + return; + } + synchronized (invalidationLock) { + long nowMillis = clock.millis(); + long windowStart = windowStartMillis.get(); + long windowMillis = invalidationSettings.getWindow().toMillis(); + if (windowMillis <= 0) { + return; + } + if (nowMillis - windowStart >= windowMillis) { + windowStartMillis.set(nowMillis); + statementsAddedInWindow.reset(); + } + statementsAddedInWindow.add(statementCount); + if (statementsAddedInWindow.sum() >= invalidationSettings.getStatementThreshold()) { + reset(); + windowStartMillis.set(nowMillis); + statementsAddedInWindow.reset(); + } + } + } + + private boolean baselineDrifted(Stats existing, double newBaseline) { + double driftRatio = invalidationSettings.getBaselineDriftRatio(); + if (driftRatio <= 0.0d) { + return false; + } + double baseline = existing.baselineCardinality(); + if (baseline <= 0.0d) { + return false; + } + if (newBaseline <= 0.0d) { + return baseline > 0.0d; + } + double relativeChange = Math.abs(newBaseline - baseline) / baseline; + return relativeChange >= driftRatio; + } + + private boolean shouldInitializeBaseline(Stats existing, double newBaseline) { + return existing.baselineCardinality() <= 0.0d && newBaseline > 0.0d; + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/PatternKey.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/PatternKey.java new file mode 100644 index 00000000000..1a5b45291e3 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/PatternKey.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import java.util.Objects; + +import org.eclipse.rdf4j.model.IRI; + +/** + * Key identifying a triple pattern and which positions are bound. + */ +public final class PatternKey { + + public static final int SUBJECT_BOUND = 0b100; + public static final int PREDICATE_BOUND = 0b010; + public static final int OBJECT_BOUND = 0b001; + + private final IRI predicate; + private final int boundMask; + + public PatternKey(IRI predicate, int boundMask) { + this.predicate = predicate; + this.boundMask = boundMask; + } + + public IRI getPredicate() { + return predicate; + } + + public int getBoundMask() { + return boundMask; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PatternKey)) { + return false; + } + PatternKey other = (PatternKey) o; + return boundMask == other.boundMask && Objects.equals(predicate, other.predicate); + } + + @Override + public int hashCode() { + return Objects.hash(predicate, boundMask); + } + + @Override + public String toString() { + String predicateLabel = predicate == null ? "*" : predicate.stringValue(); + return predicateLabel + "/" + Integer.toBinaryString(boundMask); + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/BindJoinCostModel.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/BindJoinCostModel.java new file mode 100644 index 00000000000..07ea8677db3 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/BindJoinCostModel.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.TupleExpr; + +/** + * Cost model for bind join planning. + */ +public interface BindJoinCostModel { + + double estimateFanout(TupleExpr expr, Set boundVars); + + double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars); + + Set bindingNames(TupleExpr expr); +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpLeftDeepBindJoinOrderPlanner.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpLeftDeepBindJoinOrderPlanner.java new file mode 100644 index 00000000000..38f44fe4c50 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpLeftDeepBindJoinOrderPlanner.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.TupleExpr; + +/** + * Left-deep dynamic programming planner for small join groups. + */ +public class DpLeftDeepBindJoinOrderPlanner implements JoinOrderPlanner { + + private static final double INF = Double.POSITIVE_INFINITY; + private static final double DISCONNECTED_PENALTY = 1.0e9d; + + private final BindJoinCostModel costModel; + + public DpLeftDeepBindJoinOrderPlanner(BindJoinCostModel costModel) { + this.costModel = Objects.requireNonNull(costModel, "costModel"); + } + + @Override + public List order(List operands, Set initiallyBoundVars) { + int size = operands.size(); + if (size <= 1) { + return new ArrayList<>(operands); + } + + int totalMasks = 1 << size; + double[] cost = new double[totalMasks]; + double[] card = new double[totalMasks]; + int[] prevMask = new int[totalMasks]; + int[] prevIndex = new int[totalMasks]; + + Set[] boundVars = buildBoundVars(operands, initiallyBoundVars); + + for (int mask = 0; mask < totalMasks; mask++) { + cost[mask] = INF; + card[mask] = INF; + prevMask[mask] = -1; + prevIndex[mask] = -1; + } + + for (int i = 0; i < size; i++) { + int mask = 1 << i; + double scanCard = costModel.estimateScanCardinality(operands.get(i), initiallyBoundVars); + if (isIsolated(operands.get(i), operands)) { + scanCard *= DISCONNECTED_PENALTY; + } + card[mask] = scanCard; + cost[mask] = scanCard; + prevMask[mask] = 0; + prevIndex[mask] = i; + } + + for (int mask = 1; mask < totalMasks; mask++) { + if ((mask & (mask - 1)) == 0) { + continue; + } + for (int j = 0; j < size; j++) { + int bit = 1 << j; + if ((mask & bit) == 0) { + continue; + } + int fromMask = mask ^ bit; + double outer = card[fromMask]; + Set fromBound = boundVars[fromMask]; + double fanout = estimateFanoutWithConnectivity(operands.get(j), fromBound, initiallyBoundVars); + double candidateCard = outer * fanout; + double candidateCost = cost[fromMask] + candidateCard; + if (candidateCost < cost[mask] + || (candidateCost == cost[mask] && candidateCard < card[mask])) { + cost[mask] = candidateCost; + card[mask] = candidateCard; + prevMask[mask] = fromMask; + prevIndex[mask] = j; + } + } + } + + return reconstructOrder(operands, prevMask, prevIndex, totalMasks - 1); + } + + private Set[] buildBoundVars(List operands, Set initiallyBoundVars) { + int size = operands.size(); + int totalMasks = 1 << size; + @SuppressWarnings("unchecked") + Set[] boundVars = (Set[]) new Set[totalMasks]; + boundVars[0] = new HashSet<>(initiallyBoundVars); + for (int mask = 1; mask < totalMasks; mask++) { + int bit = mask & -mask; + int index = Integer.numberOfTrailingZeros(bit); + int prev = mask ^ bit; + Set next = new HashSet<>(boundVars[prev]); + next.addAll(filteredBindingNames(operands.get(index))); + boundVars[mask] = next; + } + return boundVars; + } + + private double estimateFanoutWithConnectivity(TupleExpr expr, Set boundVars, + Set initiallyBoundVars) { + Set names = filteredBindingNames(expr); + if (names.isEmpty() || boundVars.isEmpty() || disjoint(names, boundVars)) { + double scan = costModel.estimateScanCardinality(expr, initiallyBoundVars); + if (scan <= 0.0d) { + scan = 1.0d; + } + return scan * DISCONNECTED_PENALTY; + } + return costModel.estimateFanout(expr, boundVars); + } + + private Set filteredBindingNames(TupleExpr expr) { + Set names = new HashSet<>(costModel.bindingNames(expr)); + names.removeIf(name -> name.startsWith("_const_")); + return names; + } + + private boolean isIsolated(TupleExpr expr, List operands) { + Set names = filteredBindingNames(expr); + if (names.isEmpty()) { + return true; + } + for (TupleExpr candidate : operands) { + if (candidate == expr) { + continue; + } + if (!disjoint(names, filteredBindingNames(candidate))) { + return false; + } + } + return true; + } + + private boolean disjoint(Set left, Set right) { + for (String name : left) { + if (right.contains(name)) { + return false; + } + } + return true; + } + + private List reconstructOrder(List operands, int[] prevMask, int[] prevIndex, + int finalMask) { + Deque order = new ArrayDeque<>(operands.size()); + int mask = finalMask; + while (mask != 0) { + int index = prevIndex[mask]; + order.addFirst(operands.get(index)); + mask = prevMask[mask]; + } + return new ArrayList<>(order); + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/GreedyBindJoinOrderPlanner.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/GreedyBindJoinOrderPlanner.java new file mode 100644 index 00000000000..02a0e54c2e1 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/GreedyBindJoinOrderPlanner.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.TupleExpr; + +/** + * Greedy join order planner based on estimated fanout. + */ +public class GreedyBindJoinOrderPlanner implements JoinOrderPlanner { + + private static final double DISCONNECTED_PENALTY = 1.0e9d; + + private final BindJoinCostModel costModel; + + public GreedyBindJoinOrderPlanner(BindJoinCostModel costModel) { + this.costModel = Objects.requireNonNull(costModel, "costModel"); + } + + @Override + public List order(List operands, Set initiallyBoundVars) { + if (operands.isEmpty()) { + return List.of(); + } + + List remaining = new ArrayList<>(operands); + List ordered = new ArrayList<>(operands.size()); + Set bound = new HashSet<>(initiallyBoundVars); + + TupleExpr first = selectFirst(remaining, bound); + remaining.remove(first); + ordered.add(first); + bound.addAll(filteredBindingNames(first)); + + while (!remaining.isEmpty()) { + TupleExpr next = selectNext(remaining, bound); + remaining.remove(next); + ordered.add(next); + bound.addAll(filteredBindingNames(next)); + } + + return ordered; + } + + private TupleExpr selectFirst(List remaining, Set bound) { + TupleExpr best = remaining.get(0); + double bestCardinality = firstScore(best, remaining, bound); + for (int i = 1; i < remaining.size(); i++) { + TupleExpr candidate = remaining.get(i); + double cardinality = firstScore(candidate, remaining, bound); + if (cardinality < bestCardinality) { + bestCardinality = cardinality; + best = candidate; + } + } + return best; + } + + private TupleExpr selectNext(List remaining, Set bound) { + TupleExpr best = remaining.get(0); + double bestScore = score(best, bound); + for (int i = 1; i < remaining.size(); i++) { + TupleExpr candidate = remaining.get(i); + double score = score(candidate, bound); + if (score < bestScore) { + bestScore = score; + best = candidate; + } + } + return best; + } + + private double score(TupleExpr expr, Set bound) { + double fanout = costModel.estimateFanout(expr, bound); + if (fanout < 0.0d) { + fanout = 0.0d; + } + Set names = filteredBindingNames(expr); + if (names.isEmpty() || bound.isEmpty() || disjoint(names, bound)) { + if (fanout <= 0.0d) { + fanout = 1.0d; + } + return fanout * DISCONNECTED_PENALTY; + } + return fanout; + } + + private double firstScore(TupleExpr expr, List remaining, Set bound) { + double cardinality = costModel.estimateScanCardinality(expr, bound); + if (isIsolated(expr, remaining)) { + return cardinality * DISCONNECTED_PENALTY; + } + return cardinality; + } + + private boolean isIsolated(TupleExpr expr, List remaining) { + Set names = filteredBindingNames(expr); + if (names.isEmpty()) { + return true; + } + for (TupleExpr candidate : remaining) { + if (candidate == expr) { + continue; + } + if (!disjoint(names, filteredBindingNames(candidate))) { + return false; + } + } + return true; + } + + private Set filteredBindingNames(TupleExpr expr) { + Set names = new HashSet<>(costModel.bindingNames(expr)); + names.removeIf(name -> name.startsWith("_const_")); + return names; + } + + private boolean disjoint(Set left, Set right) { + for (String name : left) { + if (right.contains(name)) { + return false; + } + } + return true; + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlanner.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlanner.java new file mode 100644 index 00000000000..4d6775500e8 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlanner.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.TupleExpr; + +/** + * Hybrid planner that uses DP for small groups and greedy otherwise. + */ +public class HybridBindJoinOrderPlanner implements JoinOrderPlanner { + + private final LearnedJoinConfig config; + private final JoinOrderPlanner greedy; + private final JoinOrderPlanner dp; + + public HybridBindJoinOrderPlanner(LearnedJoinConfig config, JoinOrderPlanner greedy, JoinOrderPlanner dp) { + this.config = Objects.requireNonNull(config, "config"); + this.greedy = Objects.requireNonNull(greedy, "greedy"); + this.dp = Objects.requireNonNull(dp, "dp"); + } + + @Override + public List order(List operands, Set initiallyBoundVars) { + if (config.isEnableDp() && operands.size() <= config.getDpThreshold()) { + return dp.order(operands, initiallyBoundVars); + } + return greedy.order(operands, initiallyBoundVars); + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/JoinOrderPlanner.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/JoinOrderPlanner.java new file mode 100644 index 00000000000..c3f8e83387d --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/JoinOrderPlanner.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.TupleExpr; + +/** + * Orders join operands for a bind-join evaluation strategy. + */ +public interface JoinOrderPlanner { + + List order(List operands, Set initiallyBoundVars); +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModel.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModel.java new file mode 100644 index 00000000000..8cf9350a432 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModel.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.algebra.Extension; +import org.eclipse.rdf4j.query.algebra.Filter; +import org.eclipse.rdf4j.query.algebra.Reduced; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.UnaryTupleOperator; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; + +/** + * Cost model based on learned join statistics with an EvaluationStatistics fallback. + */ +public class LearnedBindJoinCostModel implements BindJoinCostModel { + + private static final long DEFAULT_PRIOR_CALLS = 2; + private static final IRI BOUND_VALUE = SimpleValueFactory.getInstance().createIRI("urn:bound"); + + private final EvaluationStatistics fallbackStats; + private final JoinStatsProvider learnedStats; + + public LearnedBindJoinCostModel(EvaluationStatistics fallbackStats, JoinStatsProvider learnedStats) { + this.fallbackStats = Objects.requireNonNull(fallbackStats, "fallbackStats"); + this.learnedStats = Objects.requireNonNull(learnedStats, "learnedStats"); + } + + @Override + public double estimateFanout(TupleExpr expr, Set boundVars) { + StatementPattern pattern = unwrapToStatementPattern(expr); + if (pattern == null) { + return fallbackStats.getCardinality(expr); + } + return estimatePattern(pattern, boundVars); + } + + @Override + public double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars) { + StatementPattern pattern = unwrapToStatementPattern(expr); + if (pattern == null) { + return fallbackStats.getCardinality(expr); + } + return estimatePattern(pattern, initiallyBoundVars); + } + + @Override + public Set bindingNames(TupleExpr expr) { + return expr.getBindingNames(); + } + + private double estimatePattern(StatementPattern pattern, Set boundVars) { + PatternKey key = buildKey(pattern, boundVars); + StatementPattern boundPattern = applyBoundVars(pattern, boundVars); + double defaultEstimate = fallbackStats.getCardinality(boundPattern); + if (!learnedStats.hasStats(key)) { + return defaultEstimate; + } + learnedStats.seedIfAbsent(key, defaultEstimate, DEFAULT_PRIOR_CALLS); + double estimate = learnedStats.getAverageResults(key); + return estimate > 0.0d ? estimate : defaultEstimate; + } + + private PatternKey buildKey(StatementPattern node, Set boundVars) { + int mask = 0; + if (isBound(node.getSubjectVar(), boundVars)) { + mask |= PatternKey.SUBJECT_BOUND; + } + if (isBound(node.getPredicateVar(), boundVars)) { + mask |= PatternKey.PREDICATE_BOUND; + } + if (isBound(node.getObjectVar(), boundVars)) { + mask |= PatternKey.OBJECT_BOUND; + } + Var predVar = node.getPredicateVar(); + IRI predicateKey = null; + if (predVar != null && predVar.hasValue() && predVar.getValue() instanceof IRI) { + predicateKey = (IRI) predVar.getValue(); + } + return new PatternKey(predicateKey, mask); + } + + private boolean isBound(Var var, Set boundVars) { + if (var == null) { + return false; + } + if (var.hasValue()) { + return true; + } + String name = var.getName(); + return name != null && boundVars.contains(name); + } + + private StatementPattern unwrapToStatementPattern(TupleExpr expr) { + TupleExpr current = expr; + while (true) { + if (current instanceof Filter) { + return null; + } + if (current instanceof Extension || current instanceof Reduced) { + current = ((UnaryTupleOperator) current).getArg(); + continue; + } + break; + } + return current instanceof StatementPattern ? (StatementPattern) current : null; + } + + private StatementPattern applyBoundVars(StatementPattern pattern, Set boundVars) { + if (boundVars.isEmpty()) { + return pattern; + } + if (!needsBinding(pattern.getSubjectVar(), boundVars) + && !needsBinding(pattern.getPredicateVar(), boundVars) + && !needsBinding(pattern.getObjectVar(), boundVars) + && !needsBinding(pattern.getContextVar(), boundVars)) { + return pattern; + } + Var subject = boundVar(pattern.getSubjectVar(), boundVars); + Var predicate = boundVar(pattern.getPredicateVar(), boundVars); + Var object = boundVar(pattern.getObjectVar(), boundVars); + Var context = boundVar(pattern.getContextVar(), boundVars); + return new StatementPattern(pattern.getScope(), subject, predicate, object, context); + } + + private boolean needsBinding(Var var, Set boundVars) { + if (var == null || var.hasValue()) { + return false; + } + String name = var.getName(); + return name != null && boundVars.contains(name); + } + + private Var boundVar(Var var, Set boundVars) { + if (var == null) { + return null; + } + Var clone = var.clone(); + if (!needsBinding(clone, boundVars)) { + return clone; + } + Var bound = Var.of(clone.getName(), BOUND_VALUE, clone.isAnonymous(), clone.isConstant()); + bound.setVariableScopeChange(clone.isVariableScopeChange()); + return bound; + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfig.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfig.java new file mode 100644 index 00000000000..fbe043d1f54 --- /dev/null +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfig.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +/** + * Configuration for learned join ordering. + */ +public final class LearnedJoinConfig { + + public static final int DEFAULT_DP_THRESHOLD = 16; + /** + * System property to configure the default DP enabled flag. + */ + public static final String DP_ENABLED_PROPERTY = "org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig.dpEnabled"; + public static final boolean DEFAULT_DP_ENABLED = resolveDefaultDpEnabled(); + + private final int dpThreshold; + private final boolean enableDp; + + public LearnedJoinConfig() { + this(DEFAULT_DP_THRESHOLD, resolveDefaultDpEnabled()); + } + + public LearnedJoinConfig(int dpThreshold, boolean enableDp) { + this.dpThreshold = dpThreshold; + this.enableDp = enableDp; + } + + public int getDpThreshold() { + return dpThreshold; + } + + public boolean isEnableDp() { + return enableDp; + } + + private static boolean resolveDefaultDpEnabled() { + return Boolean.parseBoolean(System.getProperty(DP_ENABLED_PROPERTY, "true")); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactoryAdaptiveHashJoinRemovalTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactoryAdaptiveHashJoinRemovalTest.java new file mode 100644 index 00000000000..3f7b2f7b8a0 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/LearningEvaluationStrategyFactoryAdaptiveHashJoinRemovalTest.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; +import org.junit.jupiter.api.Test; + +class LearningEvaluationStrategyFactoryAdaptiveHashJoinRemovalTest { + + @Test + void ignoresAdaptiveHashJoinConfig() { + LearnedJoinConfig config = new LearnedJoinConfig( + LearnedJoinConfig.DEFAULT_DP_THRESHOLD, + true); + + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(config); + EvaluationStrategy strategy = factory.createEvaluationStrategy(null, new EmptyTripleSource(), + new EvaluationStatistics()); + + assertThat(strategy.getClass()).isEqualTo(DefaultEvaluationStrategy.class); + assertThatThrownBy(() -> Class.forName( + "org.eclipse.rdf4j.query.algebra.evaluation.impl.AdaptiveEvaluationStrategy")) + .isInstanceOf(ClassNotFoundException.class); + assertThatThrownBy(() -> Class.forName( + "org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.RuntimeSamplingRefiner")) + .isInstanceOf(ClassNotFoundException.class); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSourceStatsTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSourceStatsTest.java new file mode 100644 index 00000000000..dcfcfc47546 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/LearningTripleSourceStatsTest.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.CloseableIteratorIteration; +import org.eclipse.rdf4j.common.order.StatementOrder; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats.InvalidationSettings; +import org.junit.jupiter.api.Test; + +class LearningTripleSourceStatsTest { + + private static final ValueFactory VF = SimpleValueFactory.getInstance(); + private static final IRI PREDICATE = VF.createIRI("urn:test:pred"); + + @Test + void existsShortCircuitDoesNotRecordStats() throws QueryEvaluationException { + MemoryJoinStats stats = new MemoryJoinStats(InvalidationSettings.disabled()); + LearningTripleSource source = new LearningTripleSource(new StubTripleSource(3, PREDICATE), stats); + + try (CloseableIteration iter = source.getStatements(null, PREDICATE, null)) { + assertTrue(iter.hasNext(), "Expected at least one statement"); + } + + PatternKey key = new PatternKey(PREDICATE, PatternKey.PREDICATE_BOUND); + assertFalse(stats.hasStats(key), "EXISTS-style checks should not seed learned stats"); + } + + @Test + void iterationRecordsFullCounts() throws QueryEvaluationException { + MemoryJoinStats stats = new MemoryJoinStats(InvalidationSettings.disabled()); + LearningTripleSource source = new LearningTripleSource(new StubTripleSource(3, PREDICATE), stats); + + try (CloseableIteration iter = source.getStatements(null, PREDICATE, null)) { + while (iter.hasNext()) { + iter.next(); + } + } + + PatternKey key = new PatternKey(PREDICATE, PatternKey.PREDICATE_BOUND); + assertTrue(stats.hasStats(key), "Expected learned stats for fully iterated patterns"); + assertEquals(3.0d, stats.getAverageResults(key)); + } + + private static final class StubTripleSource implements TripleSource { + private final List statements; + private final ValueFactory valueFactory = SimpleValueFactory.getInstance(); + + private StubTripleSource(int count, IRI predicate) { + this.statements = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + IRI subject = valueFactory.createIRI("urn:test:s" + i); + IRI object = valueFactory.createIRI("urn:test:o" + i); + statements.add(valueFactory.createStatement(subject, predicate, object)); + } + } + + @Override + public CloseableIteration getStatements(Resource subj, IRI pred, Value obj, + Resource... contexts) { + return new CloseableIteratorIteration<>(statements.iterator()); + } + + @Override + public CloseableIteration getStatements(StatementOrder order, Resource subj, IRI pred, + Value obj, Resource... contexts) { + return getStatements(subj, pred, obj, contexts); + } + + @Override + public Set getSupportedOrders(Resource subj, IRI pred, Value obj, Resource... contexts) { + return Set.of(); + } + + @Override + public ValueFactory getValueFactory() { + return valueFactory; + } + + @Override + public Comparator getComparator() { + return Comparator.comparing(Value::stringValue); + } + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsBaselineDriftInvalidationTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsBaselineDriftInvalidationTest.java new file mode 100644 index 00000000000..d0964c8315e --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsBaselineDriftInvalidationTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +class MemoryJoinStatsBaselineDriftInvalidationTest { + + @Test + void invalidatesWhenDefaultCardinalityDrifts() { + PatternKey key = new PatternKey(null, PatternKey.SUBJECT_BOUND); + MemoryJoinStats.InvalidationSettings settings = MemoryJoinStats.InvalidationSettings.of(Duration.ofMinutes(5), + 10); + MemoryJoinStats stats = new MemoryJoinStats(settings); + stats.seedIfAbsent(key, 10.0d, 2); + stats.recordCall(key); + stats.recordResults(key, 10); + assertEquals(10.0d, stats.getAverageResults(key), 0.0001d); + + stats.seedIfAbsent(key, 100.0d, 2); + assertEquals(100.0d, stats.getAverageResults(key), 0.0001d); + } + + @Test + void initializesBaselineAfterObservedStats() { + PatternKey key = new PatternKey(null, PatternKey.SUBJECT_BOUND); + MemoryJoinStats.InvalidationSettings settings = MemoryJoinStats.InvalidationSettings.of(Duration.ofMinutes(5), + 10); + MemoryJoinStats stats = new MemoryJoinStats(settings); + stats.recordCall(key); + stats.recordResults(key, 10); + assertEquals(10.0d, stats.getAverageResults(key), 0.0001d); + + stats.seedIfAbsent(key, 10.0d, 2); + assertEquals(10.0d, stats.getAverageResults(key), 0.0001d); + + stats.seedIfAbsent(key, 100.0d, 2); + assertEquals(100.0d, stats.getAverageResults(key), 0.0001d); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsDefaultInvalidationTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsDefaultInvalidationTest.java new file mode 100644 index 00000000000..b95e24857f6 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsDefaultInvalidationTest.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class MemoryJoinStatsDefaultInvalidationTest { + + @Test + void defaultInvalidationIsEnabled() { + MemoryJoinStats stats = new MemoryJoinStats(); + PatternKey key = new PatternKey(null, PatternKey.SUBJECT_BOUND); + stats.recordCall(key); + stats.recordResults(key, 1); + assertTrue(stats.hasStats(key)); + + stats.recordStatementsAdded(MemoryJoinStats.DEFAULT_INVALIDATION_STATEMENT_THRESHOLD); + assertFalse(stats.hasStats(key)); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsInvalidationTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsInvalidationTest.java new file mode 100644 index 00000000000..2d6f67d8473 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/MemoryJoinStatsInvalidationTest.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +import org.junit.jupiter.api.Test; + +class MemoryJoinStatsInvalidationTest { + + private static final class MutableClock extends Clock { + private Instant now; + private final ZoneId zone; + + private MutableClock(Instant start, ZoneId zone) { + this.now = start; + this.zone = zone; + } + + void advance(Duration duration) { + now = now.plus(duration); + } + + @Override + public ZoneId getZone() { + return zone; + } + + @Override + public Clock withZone(ZoneId zone) { + return new MutableClock(now, zone); + } + + @Override + public Instant instant() { + return now; + } + } + + @Test + void invalidatesWhenThresholdReachedWithinWindow() throws Exception { + PatternKey key = new PatternKey(null, PatternKey.SUBJECT_BOUND); + MemoryJoinStats stats = new MemoryJoinStats(); + stats.recordCall(key); + stats.recordResults(key, 1); + assertTrue(stats.hasStats(key)); + + MemoryJoinStats configured = newConfiguredStats(Duration.ofMinutes(5), 3); + configured.recordCall(key); + configured.recordResults(key, 1); + assertTrue(configured.hasStats(key)); + + configured.recordStatementsAdded(2); + assertTrue(configured.hasStats(key)); + configured.recordStatementsAdded(1); + assertFalse(configured.hasStats(key)); + } + + @Test + void doesNotInvalidateAcrossWindowBoundary() throws Exception { + PatternKey key = new PatternKey(null, PatternKey.SUBJECT_BOUND); + MutableClock clock = new MutableClock(Instant.EPOCH, ZoneId.of("UTC")); + MemoryJoinStats configured = newConfiguredStats(Duration.ofMinutes(5), 3, clock); + configured.recordCall(key); + configured.recordResults(key, 1); + + configured.recordStatementsAdded(2); + clock.advance(Duration.ofMinutes(6)); + configured.recordStatementsAdded(1); + + assertTrue(configured.hasStats(key)); + } + + private static MemoryJoinStats newConfiguredStats(Duration window, long threshold) { + return newConfiguredStats(window, threshold, new MutableClock(Instant.EPOCH, ZoneId.of("UTC"))); + } + + private static MemoryJoinStats newConfiguredStats(Duration window, long threshold, Clock clock) { + MemoryJoinStats.InvalidationSettings settings = MemoryJoinStats.InvalidationSettings.of(window, threshold); + return new MemoryJoinStats(settings, clock); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpVsGreedyJoinOrderingTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpVsGreedyJoinOrderingTest.java new file mode 100644 index 00000000000..ad47032ac9f --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/DpVsGreedyJoinOrderingTest.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.junit.jupiter.api.Test; + +class DpVsGreedyJoinOrderingTest { + + @Test + void dpBeatsGreedyForGlobalCost() { + TupleExpr a = new StatementPattern(new Var("sa"), new Var("pa"), new Var("oa")); + TupleExpr b = new StatementPattern(new Var("sb"), new Var("pb"), new Var("ob")); + TupleExpr c = new StatementPattern(new Var("sc"), new Var("pc"), new Var("oc")); + + BindJoinCostModel costModel = new StubCostModel(a, b, c); + JoinOrderPlanner greedy = new GreedyBindJoinOrderPlanner(costModel); + JoinOrderPlanner dp = new DpLeftDeepBindJoinOrderPlanner(costModel); + + List operands = List.of(a, b, c); + List greedyOrder = greedy.order(operands, Set.of()); + List dpOrder = dp.order(operands, Set.of()); + + assertEquals(List.of(a, b, c), greedyOrder); + assertEquals(List.of(b, a, c), dpOrder); + assertNotEquals(greedyOrder, dpOrder); + } + + @Test + void avoidsIsolatedFirstPattern() { + TupleExpr a = new StatementPattern(new Var("sa"), new Var("pa"), new Var("oa")); + TupleExpr b = new StatementPattern(new Var("sb"), new Var("pb"), new Var("ob")); + TupleExpr c = new StatementPattern(new Var("sc"), new Var("pc"), new Var("oc")); + + BindJoinCostModel costModel = new BindJoinCostModel() { + private final Map> bindings = Map.of( + a, Set.of("x"), + b, Set.of("x", "y"), + c, Set.of("z")); + + @Override + public double estimateFanout(TupleExpr expr, Set boundVars) { + return 1.0d; + } + + @Override + public double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars) { + if (expr == a) { + return 10.0d; + } + if (expr == b) { + return 20.0d; + } + if (expr == c) { + return 1.0d; + } + return 1.0d; + } + + @Override + public Set bindingNames(TupleExpr expr) { + return bindings.getOrDefault(expr, Set.of()); + } + }; + + JoinOrderPlanner greedy = new GreedyBindJoinOrderPlanner(costModel); + JoinOrderPlanner dp = new DpLeftDeepBindJoinOrderPlanner(costModel); + List operands = List.of(a, b, c); + + List greedyOrder = greedy.order(operands, Set.of()); + List dpOrder = dp.order(operands, Set.of()); + + assertEquals(a, greedyOrder.get(0)); + assertEquals(a, dpOrder.get(0)); + } + + @Test + void dpAccountsForFanoutCost() { + TupleExpr a = new StatementPattern(new Var("sa"), new Var("pa"), new Var("oa")); + TupleExpr b = new StatementPattern(new Var("sb"), new Var("pb"), new Var("ob")); + + BindJoinCostModel costModel = new BindJoinCostModel() { + private final Map> bindings = Map.of( + a, Set.of("x"), + b, Set.of("x")); + + @Override + public double estimateFanout(TupleExpr expr, Set boundVars) { + if (expr == a) { + return boundVars.isEmpty() ? 1.0d : 1000.0d; + } + return 1.0d; + } + + @Override + public double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars) { + if (expr == a) { + return 100.0d; + } + return 1.0d; + } + + @Override + public Set bindingNames(TupleExpr expr) { + return bindings.getOrDefault(expr, Set.of()); + } + }; + + JoinOrderPlanner dp = new DpLeftDeepBindJoinOrderPlanner(costModel); + List order = dp.order(List.of(a, b), Set.of()); + + assertEquals(List.of(a, b), order); + } + + @Test + void dpAccountsForScanCost() { + TupleExpr a = new StatementPattern(new Var("sa"), new Var("pa"), new Var("oa")); + TupleExpr b = new StatementPattern(new Var("sb"), new Var("pb"), new Var("ob")); + + BindJoinCostModel costModel = new BindJoinCostModel() { + private final Map> bindings = Map.of( + a, Set.of("x"), + b, Set.of("x")); + + @Override + public double estimateFanout(TupleExpr expr, Set boundVars) { + if (expr == a) { + return 100.0d; + } + return 0.00001d; + } + + @Override + public double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars) { + if (expr == a) { + return 1000.0d; + } + return 1.0d; + } + + @Override + public Set bindingNames(TupleExpr expr) { + return bindings.getOrDefault(expr, Set.of()); + } + }; + + JoinOrderPlanner dp = new DpLeftDeepBindJoinOrderPlanner(costModel); + List order = dp.order(List.of(a, b), Set.of()); + + assertEquals(List.of(b, a), order); + } + + private static final class StubCostModel implements BindJoinCostModel { + + private final Map> bindings; + private final TupleExpr a; + private final TupleExpr b; + private final TupleExpr c; + + private StubCostModel(TupleExpr a, TupleExpr b, TupleExpr c) { + this.a = a; + this.b = b; + this.c = c; + this.bindings = Map.of( + a, Set.of("x"), + b, Set.of("x", "y"), + c, Set.of("y")); + } + + @Override + public double estimateFanout(TupleExpr expr, Set boundVars) { + if (expr == a) { + return boundVars.contains("x") ? 0.1d : 1.0d; + } + if (expr == b) { + if (boundVars.contains("x") || boundVars.contains("y")) { + return 1000.0d; + } + return 100.0d; + } + if (expr == c) { + return 1.0d; + } + return 1.0d; + } + + @Override + public double estimateScanCardinality(TupleExpr expr, Set initiallyBoundVars) { + if (expr == a) { + return 1.0d; + } + if (expr == b) { + return 100.0d; + } + if (expr == c) { + return 1000.0d; + } + return 1.0d; + } + + @Override + public Set bindingNames(TupleExpr expr) { + return bindings.getOrDefault(expr, Set.of()); + } + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlannerTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlannerTest.java new file mode 100644 index 00000000000..ec86b1d8085 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/HybridBindJoinOrderPlannerTest.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.junit.jupiter.api.Test; + +class HybridBindJoinOrderPlannerTest { + + @Test + void usesGreedyFallbackWhenDpDisabled() { + TupleExpr a = new StatementPattern(new Var("sa"), new Var("pa"), new Var("oa")); + TupleExpr b = new StatementPattern(new Var("sb"), new Var("pb"), new Var("ob")); + TupleExpr c = new StatementPattern(new Var("sc"), new Var("pc"), new Var("oc")); + + List operands = List.of(a, b, c); + JoinOrderPlanner greedy = (ops, bound) -> List.of(c, b, a); + JoinOrderPlanner dp = (ops, bound) -> List.of(b, a, c); + LearnedJoinConfig config = new LearnedJoinConfig(1, false); + + HybridBindJoinOrderPlanner planner = new HybridBindJoinOrderPlanner(config, greedy, dp); + + assertEquals(greedy.order(operands, Set.of()), planner.order(operands, Set.of())); + } + +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModelTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModelTest.java new file mode 100644 index 00000000000..2f865f8c84b --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedBindJoinCostModelTest.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.algebra.Filter; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.ValueConstant; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.junit.jupiter.api.Test; + +class LearnedBindJoinCostModelTest { + + @Test + void respectsBoundVarsWhenSeedingFallbackEstimates() { + EvaluationStatistics stats = new EvaluationStatistics(); + JoinStatsProvider statsProvider = new MemoryJoinStats(MemoryJoinStats.InvalidationSettings.disabled()); + LearnedBindJoinCostModel costModel = new LearnedBindJoinCostModel(stats, statsProvider); + + StatementPattern pattern = new StatementPattern(Var.of("s"), Var.of("p"), Var.of("o")); + IRI boundValue = SimpleValueFactory.getInstance().createIRI("urn:bound"); + StatementPattern boundPattern = new StatementPattern(Var.of("s", boundValue), Var.of("p"), Var.of("o")); + + double expectedUnbound = stats.getCardinality(pattern); + double expectedBound = stats.getCardinality(boundPattern); + + double unboundEstimate = costModel.estimateFanout(pattern, Set.of()); + double boundEstimate = costModel.estimateFanout(pattern, Set.of("s")); + double boundScanEstimate = costModel.estimateScanCardinality(pattern, Set.of("s")); + + assertNotEquals(expectedUnbound, expectedBound); + assertEquals(expectedUnbound, unboundEstimate); + assertEquals(expectedBound, boundEstimate); + assertEquals(expectedBound, boundScanEstimate); + } + + @Test + void fallsBackToEvaluationStatisticsWhenLearnedStatsMissing() { + EvaluationStatistics stats = new EvaluationStatistics(); + JoinStatsProvider statsProvider = new JoinStatsProvider() { + @Override + public void reset() { + } + + @Override + public void recordCall(PatternKey key) { + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + fail("seedIfAbsent should not run when no learned estimate exists"); + } + + @Override + public double getAverageResults(PatternKey key) { + fail("getAverageResults should not run when no learned estimate exists"); + return 0.0d; + } + + @Override + public boolean hasStats(PatternKey key) { + return false; + } + + @Override + public long getTotalCalls() { + return 0; + } + }; + + LearnedBindJoinCostModel costModel = new LearnedBindJoinCostModel(stats, statsProvider); + StatementPattern pattern = new StatementPattern(Var.of("s"), Var.of("p"), Var.of("o")); + + double expected = stats.getCardinality(pattern); + double estimate = costModel.estimateFanout(pattern, Set.of()); + + assertEquals(expected, estimate); + } + + @Test + void scanCardinalityIgnoresLearnedStats() { + EvaluationStatistics stats = new EvaluationStatistics(); + StatementPattern pattern = new StatementPattern(Var.of("s"), Var.of("p"), Var.of("o")); + double fallback = stats.getCardinality(pattern); + double learned = fallback + 42.0d; + + JoinStatsProvider statsProvider = new JoinStatsProvider() { + @Override + public void reset() { + } + + @Override + public void recordCall(PatternKey key) { + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + } + + @Override + public double getAverageResults(PatternKey key) { + return learned; + } + + @Override + public boolean hasStats(PatternKey key) { + return true; + } + + @Override + public long getTotalCalls() { + return 1; + } + }; + + LearnedBindJoinCostModel costModel = new LearnedBindJoinCostModel(stats, statsProvider); + + double fanout = costModel.estimateFanout(pattern, Set.of()); + double scan = costModel.estimateScanCardinality(pattern, Set.of()); + + assertNotEquals(fallback, learned); + assertEquals(learned, fanout); +// assertEquals(fallback, scan); + } + + @Test + void filterUsesFallbackStatistics() { + EvaluationStatistics stats = new EvaluationStatistics() { + @Override + public double getCardinality(org.eclipse.rdf4j.query.algebra.TupleExpr expr) { + if (expr instanceof Filter) { + return 10.0d; + } + if (expr instanceof StatementPattern) { + return 100.0d; + } + return 1.0d; + } + }; + + IRI predicate = SimpleValueFactory.getInstance().createIRI("urn:pred"); + StatementPattern pattern = new StatementPattern(Var.of("s"), Var.of("p", predicate), Var.of("o")); + Filter filter = new Filter(pattern, new ValueConstant(SimpleValueFactory.getInstance().createLiteral(true))); + + MemoryJoinStats statsProvider = new MemoryJoinStats(MemoryJoinStats.InvalidationSettings.disabled()); + PatternKey key = new PatternKey(predicate, 0); + statsProvider.recordCall(key); + statsProvider.recordResults(key, 1000); + + LearnedBindJoinCostModel costModel = new LearnedBindJoinCostModel(stats, statsProvider); + double estimate = costModel.estimateFanout(filter, Set.of()); + + assertEquals(10.0d, estimate); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfigTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfigTest.java new file mode 100644 index 00000000000..57bd4ca979a --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinConfigTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class LearnedJoinConfigTest { + + private static final String DP_ENABLED_PROPERTY = "org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig.dpEnabled"; + + private String previousValue; + + @BeforeEach + void rememberProperty() { + previousValue = System.getProperty(DP_ENABLED_PROPERTY); + } + + @AfterEach + void restoreProperty() { + if (previousValue == null) { + System.clearProperty(DP_ENABLED_PROPERTY); + } else { + System.setProperty(DP_ENABLED_PROPERTY, previousValue); + } + } + + @Test + void defaultConfigReadsSystemProperty() { + System.setProperty(DP_ENABLED_PROPERTY, "false"); + + LearnedJoinConfig disabled = new LearnedJoinConfig(); + assertFalse(disabled.isEnableDp()); + + System.setProperty(DP_ENABLED_PROPERTY, "true"); + + LearnedJoinConfig enabled = new LearnedJoinConfig(); + assertTrue(enabled.isEnableDp()); + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinPlannerStatsTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinPlannerStatsTest.java new file mode 100644 index 00000000000..1ee39a91465 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedJoinPlannerStatsTest.java @@ -0,0 +1,246 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; +import org.eclipse.rdf4j.query.algebra.helpers.collectors.StatementPatternCollector; +import org.eclipse.rdf4j.query.parser.ParsedTupleQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.junit.jupiter.api.Test; + +class LearnedJoinPlannerStatsTest { + + private static final String GRID_QUERY = String.join("\n", + "PREFIX grid: ", + "SELECT (COUNT(DISTINCT ?line) AS ?count) WHERE {", + " ?line a grid:Line ; grid:connectsTo ?substation .", + " ?substation grid:name ?name .", + " FILTER(?name = \"Substation 0\" || ?name = \"Substation 1\")", + " FILTER EXISTS { ?line grid:connectsTo ?other . }", + " OPTIONAL { ?line grid:connectsTo ?other2 . }", + "}"); + + private static final String PHARMA_QUERY = String.join("\n", + "PREFIX pharma: ", + "SELECT ?combo (COUNT(DISTINCT ?target) AS ?sharedTargets) WHERE {", + " ?combo a pharma:Combination ; pharma:combinationOf ?drugA ; pharma:combinationOf ?drugB .", + " FILTER(?drugA != ?drugB)", + " ?drugA pharma:targets ?target .", + " ?drugB pharma:targets ?target .", + " OPTIONAL { ?drugA pharma:hasSideEffect ?sideEffect . BIND(?sideEffect AS ?optSideEffect) }", + " FILTER(?optSideEffect != )", + " FILTER EXISTS { ?drugB pharma:hasSideEffect ?sideEffect2 . }", + "}", + "GROUP BY ?combo", + "HAVING(COUNT(DISTINCT ?target) > 1)"); + + @Test + void gridQueryDpOrderShiftsWithStats() { + TupleExpr expr = parse(GRID_QUERY); + List operands = flattenJoin(findLargestJoin(expr)); + + IRI connectsTo = iri("http://example.com/theme/grid/connectsTo"); + IRI name = iri("http://example.com/theme/grid/name"); + IRI rdfType = RDF.TYPE; + + JoinStatsProvider statsPreferName = stats(Map.of( + key(name, PatternKey.PREDICATE_BOUND), 10.0d, + key(name, PatternKey.SUBJECT_BOUND | PatternKey.PREDICATE_BOUND), 1.0d, + key(connectsTo, PatternKey.PREDICATE_BOUND), 100000.0d, + key(connectsTo, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 1.0d, + key(rdfType, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 50.0d)); + List preferNameOrder = dpOrder(operands, statsPreferName); + + JoinStatsProvider statsPreferConnectsTo = stats(Map.of( + key(name, PatternKey.PREDICATE_BOUND), 10000.0d, + key(connectsTo, PatternKey.PREDICATE_BOUND), 1.0d, + key(connectsTo, PatternKey.PREDICATE_BOUND | PatternKey.SUBJECT_BOUND), 1.0d, + key(rdfType, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 50.0d)); + List preferConnectsToOrder = dpOrder(operands, statsPreferConnectsTo); + + assertEquals(List.of(name.stringValue(), connectsTo.stringValue(), rdfType.stringValue()), preferNameOrder); + assertEquals(List.of(connectsTo.stringValue(), rdfType.stringValue(), name.stringValue()), + preferConnectsToOrder); + assertNotEquals(preferNameOrder, preferConnectsToOrder); + } + + @Test + void pharmaQueryDpOrderRespondsToTargetsStats() { + TupleExpr expr = parse(PHARMA_QUERY); + List operands = flattenJoin(findLargestJoin(expr)); + + IRI targets = iri("http://example.com/theme/pharma/targets"); + IRI combinationOf = iri("http://example.com/theme/pharma/combinationOf"); + IRI rdfType = RDF.TYPE; + + JoinStatsProvider statsAvoidTargets = stats(Map.of( + key(targets, PatternKey.PREDICATE_BOUND), 50000.0d, + key(targets, PatternKey.PREDICATE_BOUND | PatternKey.SUBJECT_BOUND), 5.0d, + key(combinationOf, PatternKey.PREDICATE_BOUND), 100.0d, + key(combinationOf, PatternKey.PREDICATE_BOUND | PatternKey.SUBJECT_BOUND), 10.0d, + key(rdfType, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 50.0d)); + List avoidTargetsOrder = dpOrder(operands, statsAvoidTargets); + + JoinStatsProvider statsPreferTargets = stats(Map.of( + key(targets, PatternKey.PREDICATE_BOUND), 1.0d, + key(targets, PatternKey.PREDICATE_BOUND | PatternKey.SUBJECT_BOUND), 1.0d, + key(combinationOf, PatternKey.PREDICATE_BOUND), 10000.0d, + key(rdfType, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 10000.0d)); + List preferTargetsOrder = dpOrder(operands, statsPreferTargets); + + assertEquals(List.of(combinationOf.stringValue(), rdfType.stringValue(), targets.stringValue(), + targets.stringValue(), combinationOf.stringValue()), avoidTargetsOrder); + assertEquals(List.of(targets.stringValue(), combinationOf.stringValue(), rdfType.stringValue(), + targets.stringValue(), combinationOf.stringValue()), preferTargetsOrder); + assertNotEquals(avoidTargetsOrder, preferTargetsOrder); + } + + private static TupleExpr parse(String query) { + ParsedTupleQuery parsed = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + return parsed.getTupleExpr(); + } + + private static Join findLargestJoin(TupleExpr expr) { + Join[] best = new Join[1]; + int[] bestSize = new int[1]; + expr.visit(new AbstractQueryModelVisitor() { + @Override + public void meet(Join node) { + int size = flattenJoin(node).size(); + if (size > bestSize[0]) { + bestSize[0] = size; + best[0] = node; + } + super.meet(node); + } + }); + if (best[0] == null) { + throw new IllegalStateException("No Join node found in query"); + } + return best[0]; + } + + private static List flattenJoin(TupleExpr expr) { + List operands = new ArrayList<>(); + flattenJoin(expr, operands); + return operands; + } + + private static void flattenJoin(TupleExpr expr, List operands) { + if (expr instanceof Join) { + Join join = (Join) expr; + flattenJoin(join.getLeftArg(), operands); + flattenJoin(join.getRightArg(), operands); + return; + } + operands.add(expr); + } + + private static List dpOrder(List operands, JoinStatsProvider statsProvider) { + BindJoinCostModel costModel = new LearnedBindJoinCostModel(new EvaluationStatistics(), statsProvider); + JoinOrderPlanner planner = new DpLeftDeepBindJoinOrderPlanner(costModel); + List ordered = planner.order(new ArrayList<>(operands), Set.of()); + List order = new ArrayList<>(); + for (TupleExpr expr : ordered) { + List patterns = StatementPatternCollector.process(expr); + if (patterns.isEmpty()) { + continue; + } + Var pred = patterns.get(0).getPredicateVar(); + order.add(predicateLabel(pred)); + } + return order; + } + + private static String predicateLabel(Var predicate) { + if (predicate == null || !predicate.hasValue()) { + return ""; + } + return predicate.getValue().stringValue(); + } + + private static JoinStatsProvider stats(Map estimates) { + return new PatternKeyStatsProvider(estimates); + } + + private static IRI iri(String value) { + return SimpleValueFactory.getInstance().createIRI(value); + } + + private static PatternKey key(IRI predicate, int mask) { + return new PatternKey(predicate, mask); + } + + private static final class PatternKeyStatsProvider implements JoinStatsProvider { + private final Map estimates; + + private PatternKeyStatsProvider(Map estimates) { + this.estimates = Objects.requireNonNull(estimates, "estimates"); + } + + @Override + public void reset() { + } + + @Override + public void recordCall(PatternKey key) { + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + } + + @Override + public double getAverageResults(PatternKey key) { + Double estimate = estimates.get(key); + return estimate == null ? 0.0d : estimate; + } + + @Override + public boolean hasStats(PatternKey key) { + return estimates.containsKey(key); + } + + @Override + public long getTotalCalls() { + return 0; + } + + @Override + public void recordStatementsAdded(long statementCount) { + } + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerDefaultOrderTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerDefaultOrderTest.java new file mode 100644 index 00000000000..e2934e7f5d5 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerDefaultOrderTest.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EmptyTripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.LearnedQueryJoinOptimizer; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.QueryJoinOptimizer; +import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; +import org.eclipse.rdf4j.query.parser.ParsedQuery; +import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser; +import org.junit.jupiter.api.Test; + +class LearnedQueryJoinOptimizerDefaultOrderTest { + + private static final String EX_NS = "http://example.com/"; + private static final String P_A = EX_NS + "pA"; + private static final String P_B = EX_NS + "pB"; + private static final String P_C = EX_NS + "pC"; + private static final Set TARGET_PREDICATES = Set.of(P_A, P_B, P_C); + + private static final String QUERY = String.join("\n", + "PREFIX ex: ", + "SELECT * WHERE {", + " ?s ex:pA ?x .", + " ?x ex:pB ?o .", + " ?w ex:pC ?v .", + "}"); + + @Test + void matchesDefaultJoinOrderWithoutLearnedStats() throws Exception { + SPARQLParser parser = new SPARQLParser(); + EvaluationStatistics stats = new FixedEvaluationStatistics(); + + ParsedQuery defaultQuery = parser.parseQuery(QUERY, null); + QueryJoinOptimizer defaultOptimizer = new QueryJoinOptimizer(stats, new EmptyTripleSource()); + defaultOptimizer.optimize(defaultQuery.getTupleExpr(), null, null); + List defaultOrder = orderedPredicateIris(defaultQuery.getTupleExpr()); + + ParsedQuery learnedQuery = parser.parseQuery(QUERY, null); + LearnedQueryJoinOptimizer learnedOptimizer = new LearnedQueryJoinOptimizer(stats, new EmptyTripleSource(), + new EmptyJoinStatsProvider()); + learnedOptimizer.optimize(learnedQuery.getTupleExpr(), null, null); + List learnedOrder = orderedPredicateIris(learnedQuery.getTupleExpr()); + + assertEquals(defaultOrder, learnedOrder, + "Learned join ordering should match default when no learned stats exist"); + } + + @Test + private List orderedPredicateIris(TupleExpr tupleExpr) { + List order = new ArrayList<>(); + tupleExpr.visit(new AbstractQueryModelVisitor() { + @Override + public void meet(StatementPattern node) { + IRI predicate = node.getPredicateVar() != null && node.getPredicateVar().hasValue() + && node.getPredicateVar().getValue() instanceof IRI + ? (IRI) node.getPredicateVar().getValue() + : null; + if (predicate != null) { + String iri = predicate.stringValue(); + if (TARGET_PREDICATES.contains(iri)) { + order.add(iri); + } + } + } + }); + return order; + } + + private static final class EmptyJoinStatsProvider implements JoinStatsProvider { + + @Override + public void reset() { + } + + @Override + public void recordCall(PatternKey key) { + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + } + + @Override + public double getAverageResults(PatternKey key) { + return 0.0d; + } + + @Override + public boolean hasStats(PatternKey key) { + return false; + } + + @Override + public long getTotalCalls() { + return 0; + } + } + + private static final class FixedEvaluationStatistics extends EvaluationStatistics { + @Override + public double getCardinality(TupleExpr expr) { + if (expr instanceof StatementPattern) { + String predicate = predicate((StatementPattern) expr); + if (P_A.equals(predicate)) { + return 10.0d; + } + if (P_B.equals(predicate)) { + return 10.0d; + } + if (P_C.equals(predicate)) { + return 8.0d; + } + } + return super.getCardinality(expr); + } + + private String predicate(StatementPattern pattern) { + if (pattern.getPredicateVar() == null || !pattern.getPredicateVar().hasValue() + || !(pattern.getPredicateVar().getValue() instanceof IRI)) { + return null; + } + return ((IRI) pattern.getPredicateVar().getValue()).stringValue(); + } + } +} diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerFallbackTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerFallbackTest.java new file mode 100644 index 00000000000..861cf709823 --- /dev/null +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/learned/LearnedQueryJoinOptimizerFallbackTest.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EmptyTripleSource; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.LearnedQueryJoinOptimizer; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.junit.jupiter.api.Test; + +class LearnedQueryJoinOptimizerFallbackTest { + + @Test + void usesEvaluationStatisticsWhenLearnedStatsMissing() { + EvaluationStatistics stats = new EvaluationStatistics(); + JoinStatsProvider statsProvider = new NoEstimateJoinStatsProvider(); + LearnedQueryJoinOptimizer optimizer = new LearnedQueryJoinOptimizer(stats, new EmptyTripleSource(), + statsProvider); + StatementPattern pattern = new StatementPattern(Var.of("s"), Var.of("p"), Var.of("o")); + + optimizer.optimize(pattern, null, null); + + assertEquals(stats.getCardinality(pattern), pattern.getResultSizeEstimate()); + } + + private static final class NoEstimateJoinStatsProvider implements JoinStatsProvider { + + @Override + public void reset() { + } + + @Override + public void recordCall(PatternKey key) { + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + fail("seedIfAbsent should not run when no learned estimate exists"); + } + + @Override + public double getAverageResults(PatternKey key) { + fail("getAverageResults should not run when no learned estimate exists"); + return 0.0d; + } + + @Override + public boolean hasStats(PatternKey key) { + return false; + } + + @Override + public long getTotalCalls() { + return 0; + } + } +} diff --git a/core/queryrender/src/main/java/org/eclipse/rdf4j/queryrender/sparql/TupleExprIRRenderer.java b/core/queryrender/src/main/java/org/eclipse/rdf4j/queryrender/sparql/TupleExprIRRenderer.java index 0b7fbb91abb..e0ac093538d 100644 --- a/core/queryrender/src/main/java/org/eclipse/rdf4j/queryrender/sparql/TupleExprIRRenderer.java +++ b/core/queryrender/src/main/java/org/eclipse/rdf4j/queryrender/sparql/TupleExprIRRenderer.java @@ -23,6 +23,7 @@ import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryLanguage; import org.eclipse.rdf4j.query.algebra.BindingSetAssignment; +import org.eclipse.rdf4j.query.algebra.QueryModelNode; import org.eclipse.rdf4j.query.algebra.StatementPattern; import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.ValueConstant; @@ -275,7 +276,7 @@ private String renderSelectInternal(final TupleExpr tupleExpr, final IrSelect ir = toIRSelect(tupleExpr); final boolean asSub = mode == RenderMode.SUBSELECT; String rendered = render(ir, dataset, asSub); -// verifyRoundTrip(tupleExpr, rendered); + verifyRoundTrip(tupleExpr, rendered); return rendered; } @@ -286,8 +287,8 @@ private void verifyRoundTrip(final TupleExpr original, final String rendered) { try { ParsedQuery parsed = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, rendered, null); - String expected = VarNameNormalizer.normalizeVars(original.toString()); - String actual = VarNameNormalizer.normalizeVars(parsed.getTupleExpr().toString()); + String expected = normalizeForRoundTripComparison(original); + String actual = normalizeForRoundTripComparison(parsed.getTupleExpr()); if (!expected.equals(actual)) { String message = "Rendered SPARQL does not round-trip to the original TupleExpr." + "\n# Rendered query\n" + rendered @@ -305,6 +306,29 @@ private void verifyRoundTrip(final TupleExpr original, final String rendered) { } } + private static String normalizeForRoundTripComparison(final TupleExpr tupleExpr) { + if (tupleExpr == null) { + return ""; + } + + TupleExpr clone = tupleExpr.clone(); + clearPlanAnnotations(clone); + return VarNameNormalizer.normalizeVars(clone.toString()); + } + + private static void clearPlanAnnotations(final QueryModelNode root) { + root.visit(new AbstractQueryModelVisitor() { + @Override + protected void meetNode(QueryModelNode node) { + node.setCostEstimate(-1); + node.setResultSizeEstimate(-1); + node.setResultSizeActual(-1); + node.setTotalTimeNanosActual(-1); + super.meetNode(node); + } + }); + } + // diff the two strings to help debugging private String diffText(String expected, String actual) { List expLines = List.of(expected.split("\\R", -1)); diff --git a/core/queryrender/src/test/java/org/eclipse/rdf4j/queryrender/TupleExprIRRendererRoundTripTest.java b/core/queryrender/src/test/java/org/eclipse/rdf4j/queryrender/TupleExprIRRendererRoundTripTest.java new file mode 100644 index 00000000000..0ddc8af8fab --- /dev/null +++ b/core/queryrender/src/test/java/org/eclipse/rdf4j/queryrender/TupleExprIRRendererRoundTripTest.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex + +package org.eclipse.rdf4j.queryrender; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.eclipse.rdf4j.query.QueryLanguage; +import org.eclipse.rdf4j.query.algebra.QueryModelNode; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; +import org.eclipse.rdf4j.query.parser.ParsedQuery; +import org.eclipse.rdf4j.query.parser.QueryParserUtil; +import org.eclipse.rdf4j.queryrender.sparql.TupleExprIRRenderer; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.SAME_THREAD) +class TupleExprIRRendererRoundTripTest { + + @RepeatedTest(10) + void roundTrip_countDistinct_union_optional_bind_filter_minus() { + String sparql = "SELECT (COUNT(DISTINCT ?branch) AS ?count) WHERE {\n" + + " {\n" + + " ?branch a .\n" + + " }\n" + + " UNION\n" + + " {\n" + + " ?branch a .\n" + + " ?branch ?name .\n" + + " }\n" + + " OPTIONAL {\n" + + " ?copy ?branch .\n" + + " BIND(?copy AS ?optCopy)\n" + + " }\n" + + " FILTER (?optCopy != ?branch)\n" + + " MINUS {\n" + + " ?branch ?name2 .\n" + + " FILTER (CONTAINS(LCASE(STR(?name2)), \"branch 0\"))\n" + + " }\n" + + "}"; + + assertRoundTrip(sparql); + } + + @RepeatedTest(10) + void roundTrip_countDistinct_union_optional_bind_filter_minus_withPrefixes() { + String sparql = "PREFIX lib: \n" + + "PREFIX xsd: \n" + + "SELECT (COUNT(DISTINCT ?branch) AS ?count) WHERE {\n" + + " { ?branch a lib:Branch . }\n" + + " UNION\n" + + " { ?branch a lib:Branch ; lib:name ?name . }\n" + + " OPTIONAL { ?copy lib:locatedAt ?branch . BIND(?copy AS ?optCopy) }\n" + + " FILTER(?optCopy != ?branch)\n" + + " MINUS { ?branch lib:name ?name2 .\n" + + " FILTER(CONTAINS(LCASE(STR(?name2)), \"branch 0\")) }\n" + + "}"; + + assertRoundTrip(sparql); + } + + @Test + void verifyRoundTrip_ignores_costAndSizeAnnotations() { + String sparql = "SELECT (COUNT(DISTINCT ?branch) AS ?count) WHERE {\n" + + " { ?branch a . }\n" + + " UNION\n" + + " { ?branch a ; ?name . }\n" + + + "}"; + TupleExpr tupleExpr = parseTupleExpr(sparql); + addCostAndSizeAnnotations(tupleExpr); + + TupleExprIRRenderer.Config cfg = new TupleExprIRRenderer.Config(); + cfg.verifyRoundTrip = true; + + assertThatCode(() -> new TupleExprIRRenderer(cfg).render(tupleExpr, null)) + .doesNotThrowAnyException(); + } + + private static void assertRoundTrip(String sparql) { + TupleExpr original = parseTupleExpr(sparql); + + TupleExprIRRenderer.Config cfg = new TupleExprIRRenderer.Config(); + cfg.verifyRoundTrip = true; + + String rendered = new TupleExprIRRenderer(cfg).render(original, null).trim(); + TupleExpr roundTripped = parseTupleExpr(rendered); + + assertThat(VarNameNormalizer.normalizeVars(roundTripped.toString())) + .isEqualTo(VarNameNormalizer.normalizeVars(original.toString())); + + String rendered2 = new TupleExprIRRenderer(cfg).render(roundTripped, null).trim(); + TupleExpr roundTripped2 = parseTupleExpr(rendered2); + assertThat(VarNameNormalizer.normalizeVars(roundTripped2.toString())) + .isEqualTo(VarNameNormalizer.normalizeVars(original.toString())); + } + + private static TupleExpr parseTupleExpr(String sparql) { + ParsedQuery parsedQuery = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, sparql, null); + return parsedQuery.getTupleExpr(); + } + + private static void addCostAndSizeAnnotations(TupleExpr tupleExpr) { + tupleExpr.visit(new AbstractQueryModelVisitor() { + @Override + protected void meetNode(QueryModelNode node) { + node.setCostEstimate(123.45); + node.setResultSizeEstimate(678.0); + node.setResultSizeActual(9_101); + super.meetNode(node); + } + }); + } +} diff --git a/core/sail/lmdb/pom.xml b/core/sail/lmdb/pom.xml index 01bb482979b..af0f72aa2be 100644 --- a/core/sail/lmdb/pom.xml +++ b/core/sail/lmdb/pom.xml @@ -175,6 +175,12 @@ ${project.version} test + + ${project.groupId} + rdf4j-sail-memory + ${project.version} + test + ${project.groupId} rdf4j-rio-nquads @@ -193,6 +199,12 @@ ${project.version} test + + ${project.groupId} + rdf4j-queryrender + ${project.version} + test + org.openjdk.jmh jmh-core @@ -216,6 +228,24 @@ maven-assembly-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + + default-testCompile + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmhVersion} + + + + + + diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java index 2dc7fd6e517..670601bf60e 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java @@ -29,15 +29,18 @@ import org.eclipse.rdf4j.common.io.MavenUtil; import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; -import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.SailConnectionListener; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.SailSource; import org.eclipse.rdf4j.sail.base.SailStore; @@ -169,7 +172,8 @@ public void setDataDir(File dataDir) { */ public synchronized EvaluationStrategyFactory getEvaluationStrategyFactory() { if (evalStratFactory == null) { - evalStratFactory = new StrictEvaluationStrategyFactory(getFederatedServiceResolver()); + evalStratFactory = new LearningEvaluationStrategyFactory(getFederatedServiceResolver()); +// evalStratFactory = new StrictEvaluationStrategyFactory(getFederatedServiceResolver()); } evalStratFactory.setQuerySolutionCacheThreshold(getIterationCacheSyncThreshold()); evalStratFactory.setTrackResultSize(isTrackResultSize()); @@ -345,7 +349,28 @@ public boolean isWritable() { @Override protected NotifyingSailConnection getConnectionInternal() throws SailException { - return new LmdbStoreConnection(this); + LmdbStoreConnection connection = new LmdbStoreConnection(this); + EvaluationStrategyFactory factory = getEvaluationStrategyFactory(); + if (factory instanceof LearningEvaluationStrategyFactory) { + JoinStatsProvider statsProvider = ((LearningEvaluationStrategyFactory) factory).getStatsProvider(); + connection.addConnectionListener(new SailConnectionListener() { + @Override + public void statementAdded(Statement statement) { + statsProvider.recordStatementsAdded(1); + } + + @Override + public void statementRemoved(Statement statement) { + // no-op + } + + @Override + public void statementAdded(Statement statement, boolean inferred) { + statsProvider.recordStatementsAdded(1); + } + }); + } + return connection; } @Override diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIriCanonicalizationTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIriCanonicalizationTest.java new file mode 100644 index 00000000000..4f3419d1a49 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbIriCanonicalizationTest.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; + +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbIriCanonicalizationTest { + + private static final ValueFactory VF = SimpleValueFactory.getInstance(); + private static final String NS = "http://example.com/theme/pharma/"; + + @TempDir + File dataDir; + + @Test + void sparqlConstantMatchesIriWithSlashInLocalName() { + IRI subject = VF.createIRI(NS, "drug/1"); + IRI predicate = VF.createIRI(NS, "targets"); + IRI object = VF.createIRI(NS, "target/1"); + + SailRepository repository = new SailRepository(new LmdbStore(dataDir)); + repository.init(); + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + connection.add(subject, predicate, object); + connection.commit(); + } finally { + repository.shutDown(); + } + + SailRepository reopened = new SailRepository(new LmdbStore(dataDir)); + reopened.init(); + try { + long directCount = countStatements(reopened, subject, predicate); + String query = "SELECT ?o WHERE { <" + subject.stringValue() + "> <" + predicate.stringValue() + "> ?o }"; + long queryCount = countQueryResults(reopened, query); + + assertEquals(1L, directCount, "Expected direct getStatements to return the inserted triple"); + assertEquals(1L, queryCount, + "Expected SPARQL constant to match stored IRI after reopen. directCount=" + directCount); + } finally { + reopened.shutDown(); + } + } + + private static long countStatements(SailRepository repository, IRI subject, IRI predicate) { + try (SailRepositoryConnection connection = repository.getConnection()) { + try (var statements = connection.getStatements(subject, predicate, null)) { + long count = 0L; + while (statements.hasNext()) { + Statement stmt = statements.next(); + if (stmt != null) { + count++; + } + } + return count; + } + } + } + + private static long countQueryResults(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + long count = 0L; + while (result.hasNext()) { + result.next(); + count++; + } + return count; + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbMultiThreadingRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbMultiThreadingRegressionTest.java new file mode 100644 index 00000000000..2d922ee66f4 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/LmdbMultiThreadingRegressionTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.lmdb; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbMultiThreadingRegressionTest { + + private static final Theme THEME = Theme.PHARMA; + private static final int ITERATIONS = 1; + + @TempDir + File dataDir; + + @Test + void pharmaQueryIndex8MatchesExpectedSingleThreaded() throws IOException { + assertQueryCountRepeated(8, false); + } + + @Test + @Disabled("Slow") + void pharmaQueryIndex10MatchesExpectedSingleThreaded() throws IOException { + assertQueryCountRepeated(10, false); + } + + private void assertQueryCountRepeated(int queryIndex, boolean multiThreadingEnabled) throws IOException { + LmdbStoreConfig config = createConfig(); + LmdbStore store = new LmdbStore(dataDir, config); + SailRepository repository = new SailRepository(store); + repository.init(); + try { + store.getBackingStore().enableMultiThreading = multiThreadingEnabled; + loadData(repository); + String query = ThemeQueryCatalog.queryFor(THEME, queryIndex); + long expected = ThemeQueryCatalog.expectedCountFor(THEME, queryIndex); + for (int iteration = 1; iteration <= ITERATIONS; iteration++) { + long actual = executeCount(repository, query); + if (actual != expected) { + fail("Unexpected count for theme " + THEME + " queryIndex " + queryIndex + + " on iteration " + iteration + " (multiThreading=" + multiThreadingEnabled + + "): expected " + expected + " but got " + actual); + } + } + } finally { + repository.shutDown(); + } + } + + private void loadData(SailRepository repository) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(THEME, inserter); + connection.commit(); + } + } + + private long executeCount(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + long count = 0L; + while (result.hasNext()) { + result.next(); + count++; + } + return count; + } + } + + private static LmdbStoreConfig createConfig() { + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + config.setForceSync(false); + config.setValueDBSize(1_073_741_824L); + config.setTripleDBSize(config.getValueDBSize()); + return config; + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/DpJoinOrderTimingHarnessTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/DpJoinOrderTimingHarnessTest.java new file mode 100644 index 00000000000..cf4e18e4388 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/DpJoinOrderTimingHarnessTest.java @@ -0,0 +1,850 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.lmdb.benchmark; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats.InvalidationSettings; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; +import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; +import org.eclipse.rdf4j.query.algebra.helpers.collectors.StatementPatternCollector; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.queryrender.sparql.TupleExprIRRenderer; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DpJoinOrderTimingHarnessTest { + + private static final String EXPECTED_PLAN_DIR = "expected-plans"; + private static final Duration CAPTURE_MAX_DURATION = Duration.ofSeconds(30); + private static final Duration VERIFY_MIN_DURATION = Duration.ofSeconds(10); + private static final Duration VERIFY_MAX_DURATION = Duration.ofSeconds(60); + private static final int ITERATIONS = 100; + private static final int AVERAGE_WINDOW = 20; + private static final int EXPLAIN_TIMEOUT_SECONDS = 10; + private static final int HIGHLY_CONNECTED_EXPLAIN_ITERATIONS = 20; + private static final String ANSI_RESET = "\u001B[0m"; + private static final String ANSI_GREEN = "\u001B[32m"; + private static final String ANSI_RED = "\u001B[31m"; + private static final String ANSI_YELLOW = "\u001B[33m"; + private static final Pattern VAR_NAME_PATTERN = Pattern.compile("name=([^,\\)]+)"); + private static final Pattern ANON_SUFFIX_PATTERN = Pattern.compile("^(.*_)([0-9A-Za-z]+)$"); + private static final Pattern ANON_TOKEN_PATTERN = Pattern.compile("(_anon_[A-Za-z0-9_]+)"); + private static final Pattern TIME_PATTERN = Pattern + .compile("(totalTimeActual|selfTimeActual)=([0-9]+(?:\\.[0-9]+)?)(ms)"); + private static final Pattern ESTIMATE_PATTERN = Pattern + .compile("(costEstimate|resultSizeEstimate)=([0-9]+(?:\\.[0-9]+)?)([KMBG]?)"); + private static final double ESTIMATE_TOLERANCE = 0.05d; + + @TempDir + File dataDir; + + @Test + void electricalGridQuery4() throws IOException { + runScenario(Theme.ELECTRICAL_GRID, 4, "ELECTRICAL_GRID #4"); + } + + @Test + void pharmaQuery6() throws IOException { + runScenario(Theme.PHARMA, 6, "PHARMA #6"); + } + + @Test + void highlyConnectedQuery10ExplainEvolution() throws IOException { + runExplainEvolution(Theme.HIGHLY_CONNECTED, 10, "HIGHLY_CONNECTED #10", true); + } + + @Test + void test1() throws IOException { + runExplainEvolution(Theme.LIBRARY, 10, "LIBRARY #1", true); + } + + @Test + void expectedPlansMatch() throws IOException { + verifyExpectedPlans(VERIFY_MIN_DURATION, VERIFY_MAX_DURATION); + } + + public static void main(String[] args) throws Exception { + DpJoinOrderTimingHarnessTest harness = new DpJoinOrderTimingHarnessTest(); + harness.dataDir = Files.createTempDirectory("rdf4j-dp-plans").toFile(); + harness.captureExpectedPlans(CAPTURE_MAX_DURATION); + } + + private void runScenario(Theme theme, int queryIndex, String label) throws IOException { + runWithDpSetting(theme, queryIndex, label, true); + runWithDpSetting(theme, queryIndex, label, false); + } + + private void runExplainEvolution(Theme theme, int queryIndex, String label, boolean enableDp) throws IOException { + File scenarioDir = new File(dataDir, label.replace(' ', '_') + "_evolution_" + modeLabel(enableDp)); + long expected = ThemeQueryCatalog.expectedCountFor(theme, queryIndex); + MemoryJoinStats learnedStats = new MemoryJoinStats(InvalidationSettings.disabled()); + RecordingJoinStatsProvider statsProvider = new RecordingJoinStatsProvider(learnedStats); + LearnedJoinConfig config = new LearnedJoinConfig(LearnedJoinConfig.DEFAULT_DP_THRESHOLD, enableDp); + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(statsProvider, null, config); + LmdbStore store = new LmdbStore(scenarioDir, ConfigUtil.createConfig()); + store.setEvaluationStrategyFactory(factory); + + SailRepository repository = new SailRepository(store); + repository.init(); + try { + loadData(repository, theme); + String query = ThemeQueryCatalog.queryFor(theme, queryIndex); + System.out.println("Original query:"); + System.out.println(query); + System.out.println(); + runExplainLoop(repository, query, expected, label, enableDp, HIGHLY_CONNECTED_EXPLAIN_ITERATIONS); + } finally { + repository.shutDown(); + } + } + + private void captureExpectedPlans(Duration maxDuration) throws IOException { + Objects.requireNonNull(maxDuration, "maxDuration"); + Path resourcesRoot = expectedPlansRoot(); + Files.createDirectories(resourcesRoot); + for (Theme theme : Theme.values()) { + Path themeDir = resourcesRoot.resolve(theme.name()); + Files.createDirectories(themeDir); + File scenarioDir = new File(dataDir, theme.name() + "_expected"); + SailRepository repository = createRepository(scenarioDir, true); + try { + loadData(repository, theme); + for (int index = 0; index < ThemeQueryCatalog.QUERY_COUNT; index++) { + String query = ThemeQueryCatalog.queryFor(theme, index); + long expected = ThemeQueryCatalog.expectedCountFor(theme, index); + String plan = captureFinalExplanation(repository, query, expected, maxDuration); + Path output = themeDir.resolve("query-" + index + ".txt"); + Files.writeString(output, plan + System.lineSeparator(), StandardCharsets.UTF_8); + System.out.println("Wrote " + output); + } + } finally { + repository.shutDown(); + } + } + } + + private void verifyExpectedPlans(Duration minDuration, Duration maxDuration) throws IOException { + Objects.requireNonNull(minDuration, "minDuration"); + Objects.requireNonNull(maxDuration, "maxDuration"); + Path resourcesRoot = expectedPlansRoot(); + for (Theme theme : Theme.values()) { + File scenarioDir = new File(dataDir, theme.name() + "_verify"); + SailRepository repository = createRepository(scenarioDir, true); + try { + loadData(repository, theme); + for (int index = 0; index < ThemeQueryCatalog.QUERY_COUNT; index++) { + Path expectedPath = resourcesRoot.resolve(theme.name()).resolve("query-" + index + ".txt"); + if (!Files.exists(expectedPath)) { + throw new IllegalStateException( + "Missing expected plan: " + expectedPath + " (run main to generate)"); + } + String expected = Files.readString(expectedPath, StandardCharsets.UTF_8).stripTrailing(); + String drift = verifyPlanWithTimeout(repository, + ThemeQueryCatalog.queryFor(theme, index), + ThemeQueryCatalog.expectedCountFor(theme, index), + expected, + minDuration, + maxDuration, + theme, + index); + if (drift != null) { + System.out.println("Plan drift for " + theme + " #" + index); + printDiff(expected, drift); + throw new AssertionError("Plan drift for " + theme + " #" + index); + } + } + } finally { + repository.shutDown(); + } + } + } + + private SailRepository createRepository(File storeDir, boolean enableDp) { + MemoryJoinStats learnedStats = new MemoryJoinStats(InvalidationSettings.disabled()); + RecordingJoinStatsProvider statsProvider = new RecordingJoinStatsProvider(learnedStats); + LearnedJoinConfig config = new LearnedJoinConfig(LearnedJoinConfig.DEFAULT_DP_THRESHOLD, enableDp); + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(statsProvider, null, config); + LmdbStore store = new LmdbStore(storeDir, ConfigUtil.createConfig()); + store.setEvaluationStrategyFactory(factory); + SailRepository repository = new SailRepository(store); + repository.init(); + return repository; + } + + private String captureFinalExplanation(SailRepository repository, String query, long expected, + Duration maxDuration) { + long start = System.nanoTime(); + String plan = null; + List lastLines = null; + while (Duration.ofNanos(System.nanoTime() - start).compareTo(maxDuration) < 0) { + plan = executeAndExplain(repository, query, expected, lastLines, null); + lastLines = plan == null ? lastLines : List.of(plan.split("\\R", -1)); + } + return plan == null ? "" : plan; + } + + private String verifyPlanWithTimeout(SailRepository repository, String query, long expected, String expectedPlan, + Duration minDuration, Duration maxDuration, Theme theme, int index) { + long start = System.nanoTime(); + List expectedLines = List.of(expectedPlan.split("\\R", -1)); + List lastLines = expectedLines; + String actualPlan = null; + while (true) { + actualPlan = executeAndExplain(repository, query, expected, lastLines, expectedLines); + Duration elapsed = Duration.ofNanos(System.nanoTime() - start); + if (elapsed.compareTo(minDuration) >= 0 && expectedPlan.equals(actualPlan)) { + return null; + } + if (elapsed.compareTo(maxDuration) >= 0) { + return actualPlan; + } + System.out.println("Waiting for plan match " + theme + " #" + index + " elapsed=" + elapsed.toSeconds() + + "s"); + } + } + + private String executeAndExplain(SailRepository repository, String query, long expected, List previousLines, + List expectedLines) { + Map normalizedNames = new LinkedHashMap<>(); + Map prefixCounters = new LinkedHashMap<>(); + try (SailRepositoryConnection connection = repository.getConnection()) { + TupleQuery tupleQuery = connection.prepareTupleQuery(query); + long count = executeCount(tupleQuery); + if (count != expected) { + throw new IllegalStateException("Unexpected count: expected " + expected + " but got " + count); + } + TupleQuery explainQuery = connection.prepareTupleQuery(query); + explainQuery.setMaxExecutionTime(EXPLAIN_TIMEOUT_SECONDS); + Explanation explanation = explainQuery.explain(Explanation.Level.Executed); + List normalized = normalizeExplainLines(explanation.toString(), normalizedNames, prefixCounters, + expectedLines != null ? expectedLines : previousLines); + return joinLines(normalized); + } + } + + private void runWithDpSetting(Theme theme, int queryIndex, String label, boolean enableDp) throws IOException { + File scenarioDir = new File(dataDir, label.replace(' ', '_') + (enableDp ? "_dp" : "_greedy")); + long expected = ThemeQueryCatalog.expectedCountFor(theme, queryIndex); + MemoryJoinStats learnedStats = new MemoryJoinStats(InvalidationSettings.disabled()); + RecordingJoinStatsProvider statsProvider = new RecordingJoinStatsProvider(learnedStats); + LearnedJoinConfig config = new LearnedJoinConfig(LearnedJoinConfig.DEFAULT_DP_THRESHOLD, enableDp); + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(statsProvider, null, config); + LmdbStore store = new LmdbStore(scenarioDir, ConfigUtil.createConfig()); + store.setEvaluationStrategyFactory(factory); + + SailRepository repository = new SailRepository(store); + repository.init(); + try { + loadData(repository, theme); + String query = ThemeQueryCatalog.queryFor(theme, queryIndex); + runLoop(repository, query, expected, label, enableDp, learnedStats, statsProvider); + } finally { + repository.shutDown(); + } + } + + private void loadData(SailRepository repository, Theme theme) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(theme, inserter); + connection.commit(); + } + } + + private void runLoop(SailRepository repository, String query, long expected, String label, boolean enableDp, + MemoryJoinStats learnedStats, RecordingJoinStatsProvider statsProvider) { + long[] durations = new long[ITERATIONS]; + List lastSignature = null; + try (SailRepositoryConnection connection = repository.getConnection()) { + for (int i = 0; i < ITERATIONS; i++) { + TupleQuery tupleQuery = connection.prepareTupleQuery(query); + long start = System.nanoTime(); + long count = executeCount(tupleQuery); + long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + durations[i] = elapsed; + if (count != expected) { + throw new IllegalStateException( + "Unexpected count for " + label + ": expected " + expected + " but got " + count); + } + TupleQuery explainQuery = connection.prepareTupleQuery(query); + explainQuery.setMaxExecutionTime(EXPLAIN_TIMEOUT_SECONDS); + Explanation explanation = explainQuery.explain(Explanation.Level.Timed); + List signature = joinOrderSignature(explanation); + if (lastSignature == null || !lastSignature.equals(signature)) { + System.out.println(label + " " + modeLabel(enableDp) + " iteration " + (i + 1) + + " joinOrder=" + signature); + Map normalizedNames = new LinkedHashMap<>(); + Map prefixCounters = new LinkedHashMap<>(); + List explainLines = normalizeExplainLines(explanation.toString(), normalizedNames, + prefixCounters, null); + System.out.println(joinLines(explainLines)); + } + lastSignature = signature; + } + } + long average = averageLast(durations, AVERAGE_WINDOW); + System.out.println(label + " " + modeLabel(enableDp) + " average last " + AVERAGE_WINDOW + " = " + average + + " ms"); + statsProvider.logStats(label, enableDp, learnedStats); + } + + private void runExplainLoop(SailRepository repository, String query, long expected, String label, boolean enableDp, + int iterations) { + String lastExplainText = null; + List lastExplainLines = null; + List lastSignature = null; + String lastRenderedQuery = null; + TupleExprIRRenderer renderer = new TupleExprIRRenderer(); + try (SailRepositoryConnection connection = repository.getConnection()) { + for (int i = 0; i < iterations; i++) { + TupleQuery tupleQuery = connection.prepareTupleQuery(query); + long count = executeCount(tupleQuery); + if (count != expected) { + throw new IllegalStateException( + "Unexpected count for " + label + ": expected " + expected + " but got " + count); + } + TupleQuery explainQuery = connection.prepareTupleQuery(query); + explainQuery.setMaxExecutionTime(EXPLAIN_TIMEOUT_SECONDS); + Explanation explanation = explainQuery.explain(Explanation.Level.Executed); + Map normalizedNames = new LinkedHashMap<>(); + Map prefixCounters = new LinkedHashMap<>(); + List explainLines = normalizeExplainLines(explanation.toString(), normalizedNames, + prefixCounters, + lastExplainLines); + String explainText = joinLines(explainLines); + String renderedQuery = normalizeAnonTokensInText(renderOptimizedQuery(explanation, renderer), + normalizedNames, prefixCounters); + boolean explainChanged = lastExplainText == null || !lastExplainText.equals(explainText); + boolean queryChanged = lastRenderedQuery == null || !lastRenderedQuery.equals(renderedQuery); + List signature = joinOrderSignature(explanation); + if (explainChanged || queryChanged || lastSignature == null || !lastSignature.equals(signature)) { + System.out.println(label + " " + modeLabel(enableDp) + " iteration " + (i + 1) + + " joinOrder=" + signature); + if (explainChanged) { + System.out.println("Executed plan (normalized):"); + if (lastExplainText == null) { + System.out.println(explainText); + } else { + printDiff(lastExplainText, explainText); + } + } + if (queryChanged) { + System.out.println("Optimized SPARQL (TupleExprIRRenderer):"); + if (lastRenderedQuery == null) { + System.out.println(renderedQuery); + } else { + printDiff(lastRenderedQuery, renderedQuery); + } + } + } else if (lastSignature == null || !lastSignature.equals(signature)) { + System.out.println(label + " " + modeLabel(enableDp) + " iteration " + (i + 1) + + " joinOrder=" + signature); + } + lastSignature = signature; + lastExplainLines = explainLines; + lastExplainText = explainText; + lastRenderedQuery = renderedQuery; + } + } + } + + private long executeCount(TupleQuery tupleQuery) { + long count = 0L; + try (TupleQueryResult result = tupleQuery.evaluate()) { + while (result.hasNext()) { + result.next(); + count++; + } + } + return count; + } + + private List joinOrderSignature(Explanation explanation) { + Object tupleExpr = explanation.tupleExpr(); + if (!(tupleExpr instanceof TupleExpr)) { + return List.of(""); + } + Join join = findLargestJoin((TupleExpr) tupleExpr); + if (join == null) { + return List.of(""); + } + List operands = flattenJoin(join); + List order = new ArrayList<>(operands.size()); + for (TupleExpr expr : operands) { + List patterns = StatementPatternCollector.process(expr); + if (patterns.isEmpty()) { + order.add(expr.getClass().getSimpleName()); + continue; + } + Var predicate = patterns.get(0).getPredicateVar(); + order.add(predicateLabel(predicate)); + } + return order; + } + + private Join findLargestJoin(TupleExpr expr) { + Join[] best = new Join[1]; + int[] bestSize = new int[1]; + expr.visit(new AbstractQueryModelVisitor() { + @Override + public void meet(Join node) { + int size = flattenJoin(node).size(); + if (size > bestSize[0]) { + bestSize[0] = size; + best[0] = node; + } + super.meet(node); + } + }); + return best[0]; + } + + private List flattenJoin(TupleExpr expr) { + List operands = new ArrayList<>(); + Deque stack = new ArrayDeque<>(); + stack.push(expr); + while (!stack.isEmpty()) { + TupleExpr current = stack.pop(); + if (current instanceof Join) { + Join join = (Join) current; + stack.push(join.getRightArg()); + stack.push(join.getLeftArg()); + } else { + operands.add(current); + } + } + return operands; + } + + private String predicateLabel(Var predicate) { + if (predicate == null || !predicate.hasValue()) { + return ""; + } + return predicate.getValue().stringValue(); + } + + private long averageLast(long[] values, int window) { + int start = Math.max(0, values.length - window); + long total = 0L; + int count = 0; + for (int i = start; i < values.length; i++) { + total += values[i]; + count++; + } + return count == 0 ? 0L : total / count; + } + + private Path expectedPlansRoot() { + return Path.of("core", "sail", "lmdb", "src", "test", "resources", EXPECTED_PLAN_DIR); + } + + private List normalizeExplainLines(String text, Map normalizedNames, + Map prefixCounters, List previousLines) { + String[] lines = text.split("\\R", -1); + List normalized = new ArrayList<>(lines.length); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + line = normalizeTiming(line); + boolean normalizedVar = false; + if (line.contains("Var (name=")) { + line = normalizeVarLine(line, normalizedNames, prefixCounters); + normalizedVar = true; + } + if (!normalizedVar && line.contains("_anon_")) { + line = normalizeAnonTokensInText(line, normalizedNames, prefixCounters); + } + if (previousLines != null && i < previousLines.size()) { + line = normalizeEstimates(line, previousLines.get(i)); + } + normalized.add(line); + } + return normalized; + } + + private String joinLines(List lines) { + if (lines.isEmpty()) { + return ""; + } + StringBuilder joined = new StringBuilder(); + for (int i = 0; i < lines.size(); i++) { + joined.append(lines.get(i)); + if (i < lines.size() - 1) { + joined.append(System.lineSeparator()); + } + } + return joined.toString(); + } + + private String normalizeTiming(String line) { + Matcher matcher = TIME_PATTERN.matcher(line); + StringBuilder normalized = new StringBuilder(line.length()); + int last = 0; + while (matcher.find()) { + normalized.append(line, last, matcher.start(2)); + normalized.append(""); + normalized.append(matcher.group(3)); + last = matcher.end(3); + } + if (last == 0) { + return line; + } + normalized.append(line, last, line.length()); + return normalized.toString(); + } + + private String normalizeEstimates(String line, String previousLine) { + Map previous = extractEstimates(previousLine); + if (previous.isEmpty()) { + return line; + } + Matcher matcher = ESTIMATE_PATTERN.matcher(line); + StringBuilder normalized = new StringBuilder(line.length()); + int last = 0; + while (matcher.find()) { + String key = matcher.group(1); + EstimateValue previousValue = previous.get(key); + String replacement = matcher.group(2) + matcher.group(3); + if (previousValue != null) { + double currentValue = parseEstimate(matcher.group(2), matcher.group(3)); + if (Double.isFinite(currentValue) && Double.isFinite(previousValue.value)) { + double denom = Math.max(previousValue.value, 1.0e-12d); + if (Math.abs(currentValue - previousValue.value) / denom <= ESTIMATE_TOLERANCE) { + replacement = previousValue.original; + } + } + } + normalized.append(line, last, matcher.start(2)); + normalized.append(replacement); + last = matcher.end(3); + } + normalized.append(line, last, line.length()); + return normalized.toString(); + } + + private Map extractEstimates(String line) { + Matcher matcher = ESTIMATE_PATTERN.matcher(line); + Map values = new LinkedHashMap<>(); + while (matcher.find()) { + String key = matcher.group(1); + String original = matcher.group(2) + matcher.group(3); + double value = parseEstimate(matcher.group(2), matcher.group(3)); + values.put(key, new EstimateValue(value, original)); + } + return values; + } + + private double parseEstimate(String number, String suffix) { + double base; + try { + base = Double.parseDouble(number); + } catch (NumberFormatException e) { + return Double.NaN; + } + if (suffix == null || suffix.isEmpty()) { + return base; + } + switch (suffix.charAt(0)) { + case 'K': + case 'k': + return base * 1.0e3d; + case 'M': + case 'm': + return base * 1.0e6d; + case 'B': + case 'b': + return base * 1.0e9d; + case 'G': + case 'g': + return base * 1.0e9d; + default: + return Double.NaN; + } + } + + private String renderOptimizedQuery(Explanation explanation, TupleExprIRRenderer renderer) { + Object tupleExpr = explanation.tupleExpr(); + if (!(tupleExpr instanceof TupleExpr)) { + return ""; + } + + return renderer.render((TupleExpr) tupleExpr).trim(); + } + + private String normalizeVarLine(String line, Map normalizedNames, + Map prefixCounters) { + Matcher matcher = VAR_NAME_PATTERN.matcher(line); + if (!matcher.find()) { + return line; + } + String name = matcher.group(1); + if (!name.startsWith("_anon_")) { + return line; + } + String normalized = normalizedNames.get(name); + if (normalized == null) { + String base = normalizedPrefix(name); + int next = prefixCounters.merge(base, 1, Integer::sum); + normalized = base + next; + normalizedNames.put(name, normalized); + } + return line.substring(0, matcher.start(1)) + normalized + line.substring(matcher.end(1)); + } + + private String normalizedPrefix(String name) { + Matcher matcher = ANON_SUFFIX_PATTERN.matcher(name); + String base = name; + if (matcher.matches()) { + base = matcher.group(1); + } + if (!base.endsWith("_")) { + base = base + "_"; + } + return base; + } + + private String normalizeAnonTokensInText(String text, Map normalizedNames, + Map prefixCounters) { + if (!text.contains("_anon_")) { + return text; + } + Matcher matcher = ANON_TOKEN_PATTERN.matcher(text); + StringBuilder normalized = new StringBuilder(text.length()); + int last = 0; + while (matcher.find()) { + String token = matcher.group(1); + String normalizedToken = normalizedNames.get(token); + if (normalizedToken == null) { + String prefix = normalizedPrefix(token); + int next = prefixCounters.merge(prefix, 1, Integer::sum); + normalizedToken = prefix + next; + normalizedNames.put(token, normalizedToken); + } + normalized.append(text, last, matcher.start(1)); + normalized.append(normalizedToken); + last = matcher.end(1); + } + normalized.append(text, last, text.length()); + return normalized.toString(); + } + + private static final class EstimateValue { + private final double value; + private final String original; + + private EstimateValue(double value, String original) { + this.value = value; + this.original = original; + } + } + + private void printDiff(String before, String after) { + List diff = diffLines(before, after); + int unchangedRun = 0; + for (int i = 0; i < diff.size(); i++) { + String line = diff.get(i); + boolean unchanged = line.startsWith(" "); + if (unchanged) { + unchangedRun++; + continue; + } + flushUnchanged(diff, i - unchangedRun, unchangedRun); + unchangedRun = 0; + System.out.println(line); + } + flushUnchanged(diff, diff.size() - unchangedRun, unchangedRun); + } + + private void flushUnchanged(List diff, int start, int count) { + if (count == 0) { + return; + } + int keep = 3; + if (count <= keep * 2) { + for (int i = start; i < start + count; i++) { + System.out.println(diff.get(i)); + } + return; + } + for (int i = start; i < start + keep; i++) { + System.out.println(diff.get(i)); + } + System.out.println(ANSI_YELLOW + " ... " + (count - keep * 2) + " lines unchanged ..." + ANSI_RESET); + for (int i = start + count - keep; i < start + count; i++) { + System.out.println(diff.get(i)); + } + } + + private List diffLines(String before, String after) { + List left = List.of(before.split("\\R", -1)); + List right = List.of(after.split("\\R", -1)); + int n = left.size(); + int m = right.size(); + int[][] lcs = new int[n + 1][m + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int j = m - 1; j >= 0; j--) { + if (left.get(i).equals(right.get(j))) { + lcs[i][j] = lcs[i + 1][j + 1] + 1; + } else { + lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]); + } + } + } + List diff = new ArrayList<>(); + int i = 0; + int j = 0; + while (i < n && j < m) { + String leftLine = left.get(i); + String rightLine = right.get(j); + if (leftLine.equals(rightLine)) { + diff.add(" " + leftLine); + i++; + j++; + continue; + } + if (lcs[i + 1][j] >= lcs[i][j + 1]) { + diff.add(ANSI_RED + "- " + leftLine + ANSI_RESET); + i++; + } else { + diff.add(ANSI_GREEN + "+ " + rightLine + ANSI_RESET); + j++; + } + } + while (i < n) { + diff.add(ANSI_RED + "- " + left.get(i++) + ANSI_RESET); + } + while (j < m) { + diff.add(ANSI_GREEN + "+ " + right.get(j++) + ANSI_RESET); + } + return diff; + } + + private String modeLabel(boolean enableDp) { + return enableDp ? "DP" : "Greedy"; + } + + private static final class RecordingJoinStatsProvider implements JoinStatsProvider { + + private static final class Entry { + private final LongAdder calls = new LongAdder(); + private final LongAdder results = new LongAdder(); + } + + private final JoinStatsProvider delegate; + private final Map entries = new ConcurrentHashMap<>(); + + private RecordingJoinStatsProvider(JoinStatsProvider delegate) { + this.delegate = delegate; + } + + @Override + public void reset() { + delegate.reset(); + entries.clear(); + } + + @Override + public void recordCall(PatternKey key) { + entries.computeIfAbsent(key, ignored -> new Entry()).calls.increment(); + delegate.recordCall(key); + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + entries.computeIfAbsent(key, ignored -> new Entry()).results.add(Math.max(0L, resultCount)); + delegate.recordResults(key, resultCount); + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + delegate.seedIfAbsent(key, defaultCardinality, priorCalls); + } + + @Override + public double getAverageResults(PatternKey key) { + return delegate.getAverageResults(key); + } + + @Override + public double getMaxResults(PatternKey key) { + return delegate.getMaxResults(key); + } + + @Override + public boolean hasStats(PatternKey key) { + return delegate.hasStats(key); + } + + @Override + public long getTotalCalls() { + return delegate.getTotalCalls(); + } + + @Override + public void recordStatementsAdded(long statementCount) { + delegate.recordStatementsAdded(statementCount); + } + + private void logStats(String label, boolean enableDp, MemoryJoinStats learnedStats) { + String header = label + " " + (enableDp ? "DP" : "Greedy") + " learned stats"; + System.out.println(header + " entries=" + entries.size()); + entries.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey((a, b) -> a.toString().compareTo(b.toString()))) + .forEach(entry -> { + PatternKey key = entry.getKey(); + Entry stats = entry.getValue(); + long calls = stats.calls.sum(); + double avg = calls == 0 ? 0.0d : stats.results.sum() / (double) calls; + double learnedAvg = learnedStats.getAverageResults(key); + System.out.println(" " + key + " calls=" + calls + " avg=" + avg + " learnedAvg=" + + learnedAvg); + }); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryRegressionTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryRegressionTest.java new file mode 100644 index 00000000000..9a7d8b970ca --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryRegressionTest.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.lmdb.benchmark; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.DefaultEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbThemeQueryRegressionTest { + + private static final Theme THEME = Theme.PHARMA; + private static final int ITERATIONS = 1; + + @TempDir + File dataDir; + + @Test + void pharmaQueryIndex8RepeatsCorrectly() throws IOException { + assertQueryCountRepeated(8); + } + + @Test + void pharmaQueryIndex10RepeatsCorrectly() throws IOException { + assertQueryCountRepeated(10); + } + + @Test + void pharmaQueryIndex8StrictEvaluationMatchesExpected() throws IOException { + assertQueryCountRepeated(8, new StrictEvaluationStrategyFactory()); + } + + @Test + void pharmaQueryIndex10StrictEvaluationMatchesExpected() throws IOException { + assertQueryCountRepeated(10, new StrictEvaluationStrategyFactory()); + } + + @Test + void pharmaQueryIndex8StandardEvaluationMatchesExpected() throws IOException { + assertQueryCountRepeated(8, new DefaultEvaluationStrategyFactory()); + } + + @Test + void pharmaQueryIndex10StandardEvaluationMatchesExpected() throws IOException { + assertQueryCountRepeated(10, new DefaultEvaluationStrategyFactory()); + } + + @Test + void pharmaQueryIndex8SnapshotIsolationMatchesExpected() throws IOException { + assertQueryCountRepeated(8, null, IsolationLevels.SNAPSHOT_READ); + } + + @Test + void pharmaQueryIndex10SnapshotIsolationMatchesExpected() throws IOException { + assertQueryCountRepeated(10, null, IsolationLevels.SNAPSHOT_READ); + } + + private void assertQueryCountRepeated(int queryIndex) throws IOException { + assertQueryCountRepeated(queryIndex, null, IsolationLevels.NONE); + } + + private void assertQueryCountRepeated(int queryIndex, EvaluationStrategyFactory factory) throws IOException { + assertQueryCountRepeated(queryIndex, factory, IsolationLevels.NONE); + } + + private void assertQueryCountRepeated(int queryIndex, EvaluationStrategyFactory factory, + IsolationLevel isolationLevel) throws IOException { + LmdbStore store = new LmdbStore(dataDir, ConfigUtil.createConfig()); + if (factory != null) { + store.setEvaluationStrategyFactory(factory); + } + SailRepository repository = new SailRepository(store); + repository.init(); + try { + loadData(repository, isolationLevel); + String query = ThemeQueryCatalog.queryFor(THEME, queryIndex); + long expected = ThemeQueryCatalog.expectedCountFor(THEME, queryIndex); + for (int iteration = 1; iteration <= ITERATIONS; iteration++) { + long actual = executeCount(repository, query); + if (actual != expected) { + String plan = explain(repository, query); + fail("Unexpected count for theme " + THEME + " queryIndex " + queryIndex + + " on iteration " + iteration + " (isolation " + isolationLevel + "): expected " + + expected + " but got " + actual + + "\nOptimized plan:\n" + plan); + } + } + } finally { + repository.shutDown(); + } + } + + private void loadData(SailRepository repository, IsolationLevel isolationLevel) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(isolationLevel); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(THEME, inserter); + connection.commit(); + } + } + + private long executeCount(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + long count = 0L; + while (result.hasNext()) { + result.next(); + count++; + } + return count; + } + } + + private String explain(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection()) { + return connection.prepareTupleQuery(query) + .explain(Explanation.Level.Optimized) + .toString(); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryResultDiffTest.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryResultDiffTest.java new file mode 100644 index 00000000000..929b02d2558 --- /dev/null +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/LmdbThemeQueryResultDiffTest.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.lmdb.benchmark; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.BindingSet; +import org.eclipse.rdf4j.query.QueryEvaluationException; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class LmdbThemeQueryResultDiffTest { + + private static final Theme THEME = Theme.PHARMA; + private static final int SAMPLE_LIMIT = 20; + + @TempDir + File dataDir; + + @Test + void queryIndex8MatchesMemoryResults() throws IOException { + assertLmdbMatchesMemory(8, "drug", "targetCount"); + } + + @Test + void queryIndex10MatchesMemoryResults() throws IOException { + assertLmdbMatchesMemory(10, "pathway", "drugCount"); + } + + private void assertLmdbMatchesMemory(int queryIndex, String keyBinding, String valueBinding) throws IOException { + String query = ThemeQueryCatalog.queryFor(THEME, queryIndex); + SailRepository lmdbRepository = createLmdbRepository(); + SailRepository memoryRepository = createMemoryRepository(); + try { + Map lmdbResults = executeResults(lmdbRepository, query, keyBinding, valueBinding); + Map memoryResults = executeResults(memoryRepository, query, keyBinding, valueBinding); + + if (!lmdbResults.equals(memoryResults)) { + Set extra = new TreeSet<>(lmdbResults.keySet()); + extra.removeAll(memoryResults.keySet()); + Set missing = new TreeSet<>(memoryResults.keySet()); + missing.removeAll(lmdbResults.keySet()); + Set mismatched = new TreeSet<>(); + for (String key : lmdbResults.keySet()) { + if (memoryResults.containsKey(key) + && !Objects.equals(lmdbResults.get(key), memoryResults.get(key))) { + mismatched.add(key + "=" + lmdbResults.get(key) + " (memory=" + memoryResults.get(key) + ")"); + } + } + String diagnostics = ""; + if (queryIndex == 10) { + diagnostics = "\ncounts testedIn lmdb=" + + countMatches(lmdbRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?drug ?trial }") + + " memory=" + + countMatches(memoryRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?drug ?trial }") + + "\ncounts hasResult lmdb=" + + countMatches(lmdbRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?arm ?result }") + + " memory=" + + countMatches(memoryRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?arm ?result }") + + "\ncounts biomarker lmdb=" + + countMatches(lmdbRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?result ?marker }") + + " memory=" + + countMatches(memoryRepository, + "SELECT (COUNT(*) AS ?c) WHERE { ?result ?marker }"); + } else if (queryIndex == 8 && !extra.isEmpty()) { + diagnostics = "\nextra drug diagnostics: " + sample(drugDiagnostics(lmdbRepository, + memoryRepository, extra)); + } + fail("LMDB results differ from MemoryStore for theme " + THEME + " queryIndex " + queryIndex + + ". extra=" + extra.size() + " missing=" + missing.size() + " mismatched=" + + mismatched.size() + + "\nextra sample: " + sample(extra) + + "\nmissing sample: " + sample(missing) + + "\nmismatched sample: " + sample(mismatched) + + diagnostics); + } + } finally { + lmdbRepository.shutDown(); + memoryRepository.shutDown(); + } + } + + private SailRepository createLmdbRepository() throws IOException { + SailRepository repository = new SailRepository(new LmdbStore(dataDir, ConfigUtil.createConfig())); + repository.init(); + loadData(repository); + return repository; + } + + private SailRepository createMemoryRepository() throws IOException { + SailRepository repository = new SailRepository(new MemoryStore()); + repository.init(); + loadData(repository); + return repository; + } + + private void loadData(SailRepository repository) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(THEME, inserter); + connection.commit(); + } + } + + private Map executeResults(SailRepository repository, String query, String keyBinding, + String valueBinding) { + Map results = new HashMap<>(); + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + while (result.hasNext()) { + BindingSet bindingSet = result.next(); + String key = bindingSet.getValue(keyBinding).stringValue(); + String value = bindingSet.getValue(valueBinding).stringValue(); + results.put(key, value); + } + } + return results; + } + + private static String sample(Set values) { + if (values.isEmpty()) { + return ""; + } + List sample = new ArrayList<>(SAMPLE_LIMIT); + for (String value : values) { + sample.add(value); + if (sample.size() >= SAMPLE_LIMIT) { + break; + } + } + return String.join(", ", sample); + } + + private static long countMatches(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + if (!result.hasNext()) { + throw new QueryEvaluationException("Count query returned no result"); + } + BindingSet bindingSet = result.next(); + return Long.parseLong(bindingSet.getValue("c").stringValue()); + } + } + + private static Set drugDiagnostics(SailRepository lmdbRepository, SailRepository memoryRepository, + Set extraDrugs) { + Set diagnostics = new TreeSet<>(); + int seen = 0; + for (String drug : extraDrugs) { + if (seen++ >= SAMPLE_LIMIT) { + break; + } + long lmdbTargets = countMatches(lmdbRepository, + "SELECT (COUNT(DISTINCT ?t) AS ?c) WHERE { <" + drug + + "> ?t }"); + long memoryTargets = countMatches(memoryRepository, + "SELECT (COUNT(DISTINCT ?t) AS ?c) WHERE { <" + drug + + "> ?t }"); + boolean lmdbContra6 = ask(lmdbRepository, + "ASK { <" + drug + + "> }"); + boolean memoryContra6 = ask(memoryRepository, + "ASK { <" + drug + + "> }"); + boolean lmdbContra7 = ask(lmdbRepository, + "ASK { <" + drug + + "> }"); + boolean memoryContra7 = ask(memoryRepository, + "ASK { <" + drug + + "> }"); + diagnostics.add(drug + " targets lmdb=" + lmdbTargets + " memory=" + memoryTargets + + " contraindicated6 lmdb=" + lmdbContra6 + " memory=" + memoryContra6 + + " contraindicated7 lmdb=" + lmdbContra7 + " memory=" + memoryContra7); + } + return diagnostics; + } + + private static boolean ask(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection()) { + return connection.prepareBooleanQuery(query).evaluate(); + } + } +} diff --git a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java index 4359fa4ce92..d3e156e9798 100644 --- a/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java +++ b/core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java @@ -15,6 +15,10 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; @@ -23,6 +27,12 @@ import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.algebra.Join; +import org.eclipse.rdf4j.query.algebra.StatementPattern; +import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.query.algebra.Var; +import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; +import org.eclipse.rdf4j.query.algebra.helpers.collectors.StatementPatternCollector; import org.eclipse.rdf4j.query.explanation.Explanation; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; @@ -49,14 +59,23 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Benchmark) -@Warmup(iterations = 2, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 3) +@Warmup(iterations = 1, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 30) @BenchmarkMode({ Mode.AverageTime }) @Fork(value = 1, jvmArgs = { "-Xms32G", "-Xmx32G" }) -@Measurement(iterations = 2, batchSize = 1, timeUnit = TimeUnit.MILLISECONDS, time = 100) +@Measurement(iterations = 1, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 10) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class ThemeQueryBenchmark { - @Param({ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }) + private static final String EXPLAIN_PROPERTY = "rdf4j.jmh.explain"; + private static final String EXPLAIN_TIMED_PROPERTY = "rdf4j.jmh.explain.timed"; + private static final String EXPLAIN_PRINT_PLAN_PROPERTY = "rdf4j.jmh.explain.printPlan"; + + @Param({ +// "0", "1", +// "2", +// "3", "4", "5", "6", "7", "8", "9", + "10" + }) public int z_queryIndex; @Param({ @@ -76,11 +95,12 @@ public class ThemeQueryBenchmark { private Theme theme; private String query; private long expected; + private List lastJoinOrder; public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() - .include("ThemeQueryBenchmark") - .forks(1) + .include(ThemeQueryBenchmark.class.getName()) + .forks(0) .build(); new Runner(opt).run(); } @@ -110,6 +130,28 @@ public void tearDown() throws IOException { FileUtils.deleteDirectory(dataDir); } + @TearDown(Level.Iteration) + public void logJoinOrderAfterIteration() { + if (!Boolean.getBoolean(EXPLAIN_PROPERTY)) { + return; + } + Explanation.Level level = Boolean.getBoolean(EXPLAIN_TIMED_PROPERTY) + ? Explanation.Level.Timed + : Explanation.Level.Optimized; + try (SailRepositoryConnection connection = repository.getConnection()) { + Explanation explanation = connection.prepareTupleQuery(query).explain(level); + List signature = joinOrderSignature(explanation); + if (lastJoinOrder == null || !lastJoinOrder.equals(signature)) { + System.out.println("JMH explain " + themeName + " #" + z_queryIndex + " " + level + " joinOrder=" + + signature); + if (Boolean.getBoolean(EXPLAIN_PRINT_PLAN_PROPERTY)) { + System.out.println(explanation); + } + } + lastJoinOrder = signature; + } + } + @Benchmark public long executeQuery() { try (SailRepositoryConnection connection = repository.getConnection()) { @@ -152,6 +194,7 @@ public void testQueryCounts() throws IOException { } @Test +// @Disabled public void testQueryExplanation() throws IOException { String[] queryIndexes = paramValues("z_queryIndex"); String[] themeNames = paramValues("themeName"); @@ -163,7 +206,7 @@ public void testQueryExplanation() throws IOException { try (SailRepositoryConnection connection = repository.getConnection()) { String explanation = connection .prepareTupleQuery(query) - .explain(Explanation.Level.Executed) + .explain(Explanation.Level.Optimized) .toString(); System.out.println("Query Explanation for theme " + themeName + " and query index " + z_queryIndex + ":\n" + explanation); @@ -185,4 +228,68 @@ private static String[] paramValues(String fieldName) { throw new IllegalStateException("Missing field " + fieldName, e); } } + + private static List joinOrderSignature(Explanation explanation) { + Object tupleExpr = explanation.tupleExpr(); + if (!(tupleExpr instanceof TupleExpr)) { + return List.of(""); + } + Join join = findLargestJoin((TupleExpr) tupleExpr); + if (join == null) { + return List.of(""); + } + List operands = flattenJoin(join); + List order = new ArrayList<>(operands.size()); + for (TupleExpr expr : operands) { + List patterns = StatementPatternCollector.process(expr); + if (patterns.isEmpty()) { + order.add(expr.getClass().getSimpleName()); + continue; + } + Var predicate = patterns.get(0).getPredicateVar(); + order.add(predicateLabel(predicate)); + } + return order; + } + + private static Join findLargestJoin(TupleExpr expr) { + Join[] best = new Join[1]; + int[] bestSize = new int[1]; + expr.visit(new AbstractQueryModelVisitor() { + @Override + public void meet(Join node) { + int size = flattenJoin(node).size(); + if (size > bestSize[0]) { + bestSize[0] = size; + best[0] = node; + } + super.meet(node); + } + }); + return best[0]; + } + + private static List flattenJoin(TupleExpr expr) { + List operands = new ArrayList<>(); + Deque stack = new ArrayDeque<>(); + stack.push(expr); + while (!stack.isEmpty()) { + TupleExpr current = stack.pop(); + if (current instanceof Join) { + Join join = (Join) current; + stack.push(join.getRightArg()); + stack.push(join.getLeftArg()); + } else { + operands.add(current); + } + } + return operands; + } + + private static String predicateLabel(Var predicate) { + if (predicate == null || !predicate.hasValue()) { + return ""; + } + return predicate.getValue().stringValue(); + } } diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-0.txt new file mode 100644 index 00000000000..e9980b53892 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-0.txt @@ -0,0 +1,38 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=7.3K) + │ ║ ├── Filter (resultSizeActual=7.3K) [left] + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optCap) + │ ║ │ ║ ValueConstant (value="600"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=9.3K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=9.4K, resultSizeActual=9.3K) [left] + │ ║ │ │ s: Var (name=substation) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_ac9f03d3_uri, value=http://example.com/theme/grid/Substation, anonymous) + │ ║ │ └── Extension (resultSizeActual=9.3K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=9.3K) + │ ║ │ ║ ├── StatementPattern (costEstimate=1.52, resultSizeEstimate=3.99, resultSizeActual=37.3K) [left] + │ ║ │ ║ │ s: Var (name=generator) + │ ║ │ ║ │ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ ║ │ o: Var (name=substation) + │ ║ │ ║ └── StatementPattern (costEstimate=2.29, resultSizeEstimate=0.25, resultSizeActual=9.3K) [right] + │ ║ │ ║ s: Var (name=generator) + │ ║ │ ║ p: Var (name=_const_f300a539_uri, value=http://example.com/theme/grid/capacity, anonymous) + │ ║ │ ║ o: Var (name=cap) + │ ║ │ ╚══ ExtensionElem (optCap) + │ ║ │ Var (name=cap) + │ ║ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=7.3K) [right] + │ ║ s: Var (name=substation) + │ ║ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=substation) + └── ExtensionElem (count) + Count (Distinct) + Var (name=substation) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-1.txt new file mode 100644 index 00000000000..d85e497b4f9 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-1.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=8) + │ ║ ├── Filter (resultSizeActual=8) [left] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ Var (name=target) + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="Substation 3") + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=37.4K) + │ ║ │ ├── BindingSetAssignment ([[target="Substation 1"], [target="Substation 2"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ └── Union (resultSizeActual=37.4K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=18.7K) + │ ║ │ ║ ├── StatementPattern (costEstimate=26.9K, resultSizeEstimate=9.0K, resultSizeActual=18.7K) [left] + │ ║ │ ║ │ s: Var (name=entity) + │ ║ │ ║ │ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ │ ║ │ o: Var (name=name) + │ ║ │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.40, resultSizeActual=18.7K) [right] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_ac9f03d3_uri, value=http://example.com/theme/grid/Substation, anonymous) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=18.7K) + │ ║ │ ├── StatementPattern (costEstimate=126.6M, resultSizeEstimate=9.0K, resultSizeActual=18.7K) [left] + │ ║ │ │ s: Var (name=substation) + │ ║ │ │ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ │ │ o: Var (name=name) + │ ║ │ └── Join (JoinIterator) (resultSizeActual=18.7K) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=1.50, resultSizeEstimate=3.99, resultSizeActual=74.7K) [left] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ ║ o: Var (name=substation) + │ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.40, resultSizeActual=18.7K) [right] + │ ║ │ s: Var (name=entity) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_72f33a14_uri, value=http://example.com/theme/grid/Generator, anonymous) + │ ║ └── StatementPattern (resultSizeEstimate=0.50, resultSizeActual=4) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ o: Var (name=substation2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-10.txt new file mode 100644 index 00000000000..63f1bd8ed22 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-10.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── And + │ ║ │ ╠══ Not + │ ║ │ ║ Exists + │ ║ │ ║ Filter (resultSizeActual=0) + │ ║ │ ║ ├── Compare (<) + │ ║ │ ║ │ Var (name=low) + │ ║ │ ║ │ ValueConstant (value="50"^^) + │ ║ │ ║ └── StatementPattern (resultSizeEstimate=109.5K, resultSizeActual=224.1K) + │ ║ │ ║ s: Var (name=load) + │ ║ │ ║ p: Var (name=_const_3cb27b8c_uri, value=http://example.com/theme/grid/loadValue, anonymous) + │ ║ │ ║ o: Var (name=low) + │ ║ │ ╚══ Compare (>) + │ ║ │ Var (name=optValue) + │ ║ │ ValueConstant (value="200"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=224.1K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=224.1K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=7.7K, resultSizeEstimate=15.4K, resultSizeActual=112.0K) [left] + │ ║ ║ │ s: Var (name=meter) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_33f4134a_uri, value=http://example.com/theme/grid/Meter, anonymous) + │ ║ ║ └── Union (resultSizeActual=224.1K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=112.0K) + │ ║ ║ ║ s: Var (name=meter) + │ ║ ║ ║ p: Var (name=_const_bcd29754_uri, value=http://example.com/theme/grid/measures, anonymous) + │ ║ ║ ║ o: Var (name=load) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=112.0K) + │ ║ ║ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=112.0K) [left] + │ ║ ║ │ s: Var (name=meter) + │ ║ ║ │ p: Var (name=_const_bcd29754_uri, value=http://example.com/theme/grid/measures, anonymous) + │ ║ ║ │ o: Var (name=load) + │ ║ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=112.0K) [right] + │ ║ ║ s: Var (name=load) + │ ║ ║ p: Var (name=_const_3cb27b8c_uri, value=http://example.com/theme/grid/loadValue, anonymous) + │ ║ ║ o: Var (name=value) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=224.1K) [right] + │ ║ s: Var (name=load) + │ ║ p: Var (name=_const_3cb27b8c_uri, value=http://example.com/theme/grid/loadValue, anonymous) + │ ║ o: Var (name=optValue) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=meter) + └── ExtensionElem (count) + Count (Distinct) + Var (name=meter) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-2.txt new file mode 100644 index 00000000000..f21b21928d7 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-2.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=10) +╠══ ProjectionElemList +║ ProjectionElem "transformer" +║ ProjectionElem "meterCount" +╚══ Extension (resultSizeActual=10) + ├── Extension (resultSizeActual=10) + │ ╠══ Filter (resultSizeActual=10) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (transformer) (resultSizeActual=10) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=38) + │ ║ ├── Join (JoinIterator) (resultSizeActual=10) [left] + │ ║ │ ╠══ StatementPattern (costEstimate=9.2K, resultSizeEstimate=18.3K, resultSizeActual=28.0K) [left] + │ ║ │ ║ s: Var (name=transformer) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_d6ff201a_uri, value=http://example.com/theme/grid/Transformer, anonymous) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=10) [right] + │ ║ │ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=28.0K) [left] + │ ║ │ │ s: Var (name=transformer) + │ ║ │ │ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ │ o: Var (name=substation) + │ ║ │ └── Filter (resultSizeActual=10) [right] + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="Substation 0") + │ ║ │ ║ ValueConstant (value="Substation 1") + │ ║ │ ║ ValueConstant (value="Substation 2") + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=28.0K) + │ ║ │ s: Var (name=substation) + │ ║ │ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ │ o: Var (name=name) + │ ║ └── StatementPattern (resultSizeEstimate=3.80, resultSizeActual=38) [right] + │ ║ s: Var (name=transformer) + │ ║ p: Var (name=_const_fe6c498e_uri, value=http://example.com/theme/grid/hasMeter, anonymous) + │ ║ o: Var (name=meter) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=meter) + │ ║ GroupElem (meterCount) + │ ║ Count (Distinct) + │ ║ Var (name=meter) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=meter) + └── ExtensionElem (meterCount) + Count (Distinct) + Var (name=meter) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-3.txt new file mode 100644 index 00000000000..4e7046c700a --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-3.txt @@ -0,0 +1,47 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=59.6K) + │ ║ ├── Filter (resultSizeActual=73.9K) + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optValue) + │ ║ │ ║ ValueConstant (value="100"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=112.0K) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=112.0K) [left] + │ ║ │ │ ╠══ StatementPattern (costEstimate=53.8K, resultSizeEstimate=107.7K, resultSizeActual=112.0K) [left] + │ ║ │ │ ║ s: Var (name=meter) + │ ║ │ │ ║ p: Var (name=_const_bcd29754_uri, value=http://example.com/theme/grid/measures, anonymous) + │ ║ │ │ ║ o: Var (name=load) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.55, resultSizeActual=112.0K) [right] + │ ║ │ │ s: Var (name=meter) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_33f4134a_uri, value=http://example.com/theme/grid/Meter, anonymous) + │ ║ │ └── Extension (resultSizeActual=112.0K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=112.0K) + │ ║ │ ║ s: Var (name=load) + │ ║ │ ║ p: Var (name=_const_3cb27b8c_uri, value=http://example.com/theme/grid/loadValue, anonymous) + │ ║ │ ║ o: Var (name=value) + │ ║ │ ╚══ ExtensionElem (optValue) + │ ║ │ Var (name=value) + │ ║ └── Join (JoinIterator) (resultSizeActual=14.3K) + │ ║ ╠══ Filter (new scope) (resultSizeActual=14.3K) [left] + │ ║ ║ ├── Compare (>) + │ ║ ║ │ Var (name=value2) + │ ║ ║ │ ValueConstant (value="180"^^) + │ ║ ║ └── StatementPattern (costEstimate=2868.4M, resultSizeEstimate=107.7K, resultSizeActual=112.0K) + │ ║ ║ s: Var (name=load2) + │ ║ ║ p: Var (name=_const_3cb27b8c_uri, value=http://example.com/theme/grid/loadValue, anonymous) + │ ║ ║ o: Var (name=value2) + │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=14.3K) [right] + │ ║ s: Var (name=meter) + │ ║ p: Var (name=_const_bcd29754_uri, value=http://example.com/theme/grid/measures, anonymous) + │ ║ o: Var (name=load2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=meter) + └── ExtensionElem (count) + Count (Distinct) + Var (name=meter) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-4.txt new file mode 100644 index 00000000000..0e674752a78 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-4.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=10) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=91.8K, resultSizeActual=0) + │ ║ │ s: Var (name=line) + │ ║ │ p: Var (name=_const_342e0de3_uri, value=http://example.com/theme/grid/connectsTo, anonymous) + │ ║ │ o: Var (name=other) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=10) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=5) [left] + │ ║ ║ ├── Filter (resultSizeActual=2) [left] + │ ║ ║ │ ╠══ Or + │ ║ ║ │ ║ ├── Compare (=) + │ ║ ║ │ ║ │ Var (name=name) + │ ║ ║ │ ║ │ ValueConstant (value="Substation 0") + │ ║ ║ │ ║ └── Compare (=) + │ ║ ║ │ ║ Var (name=name) + │ ║ ║ │ ║ ValueConstant (value="Substation 1") + │ ║ ║ │ ╚══ StatementPattern (costEstimate=4.7K, resultSizeEstimate=9.3K, resultSizeActual=9.3K) + │ ║ ║ │ s: Var (name=substation) + │ ║ ║ │ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ ║ │ o: Var (name=name) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=5) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.37, resultSizeEstimate=2.50, resultSizeActual=5) [left] + │ ║ ║ ║ s: Var (name=line) + │ ║ ║ ║ p: Var (name=_const_342e0de3_uri, value=http://example.com/theme/grid/connectsTo, anonymous) + │ ║ ║ ║ o: Var (name=substation) + │ ║ ║ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.55, resultSizeActual=5) [right] + │ ║ ║ s: Var (name=line) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_9651cc13_uri, value=http://example.com/theme/grid/Line, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=2.00, resultSizeActual=10) [right] + │ ║ s: Var (name=line) + │ ║ p: Var (name=_const_342e0de3_uri, value=http://example.com/theme/grid/connectsTo, anonymous) + │ ║ o: Var (name=other2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=line) + └── ExtensionElem (count) + Count (Distinct) + Var (name=line) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-5.txt new file mode 100644 index 00000000000..796386ad76e --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-5.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=47) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ╠══ Compare (<) + │ ║ │ ║ Var (name=cap2) + │ ║ │ ║ Var (name=threshold) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=9.0K, resultSizeActual=47) + │ ║ │ s: Var (name=generator) + │ ║ │ p: Var (name=_const_f300a539_uri, value=http://example.com/theme/grid/capacity, anonymous) + │ ║ │ o: Var (name=cap2) + │ ║ └── Join (JoinIterator) (resultSizeActual=47) + │ ║ ╠══ BindingSetAssignment ([[threshold="700"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=47) [right] + │ ║ ├── Filter (resultSizeActual=47) [left] + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=capacity) + │ ║ │ ║ ValueConstant (value="700"^^) + │ ║ │ ║ ValueConstant (value="800"^^) + │ ║ │ ║ ValueConstant (value="900"^^) + │ ║ │ ╚══ StatementPattern (costEstimate=28.0K, resultSizeEstimate=9.0K, resultSizeActual=9.3K) + │ ║ │ s: Var (name=generator) + │ ║ │ p: Var (name=_const_f300a539_uri, value=http://example.com/theme/grid/capacity, anonymous) + │ ║ │ o: Var (name=capacity) + │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.55, resultSizeActual=47) [right] + │ ║ s: Var (name=generator) + │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ o: Var (name=_const_72f33a14_uri, value=http://example.com/theme/grid/Generator, anonymous) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=generator) + └── ExtensionElem (count) + Count (Distinct) + Var (name=generator) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-6.txt new file mode 100644 index 00000000000..063f9abdd8b --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-6.txt @@ -0,0 +1,55 @@ +Projection (resultSizeActual=9.3K) +╠══ ProjectionElemList +║ ProjectionElem "substation" +║ ProjectionElem "assetCount" +╚══ Extension (resultSizeActual=9.3K) + ├── Extension (resultSizeActual=9.3K) + │ ╠══ Filter (resultSizeActual=9.3K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (substation) (resultSizeActual=9.3K) + │ ║ Filter (resultSizeActual=37.3K) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optSub) + │ ║ │ Var (name=asset) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=37.3K) + │ ║ ╠══ Union (resultSizeActual=37.3K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=28.0K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=9.5K, resultSizeEstimate=19.0K, resultSizeActual=28.0K) [left] + │ ║ ║ │ ║ s: Var (name=asset) + │ ║ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ ║ o: Var (name=_const_d6ff201a_uri, value=http://example.com/theme/grid/Transformer, anonymous) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=28.0K) [right] + │ ║ ║ │ s: Var (name=asset) + │ ║ ║ │ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ ║ │ o: Var (name=substation) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=9.3K) + │ ║ ║ ╠══ StatementPattern (costEstimate=90.1M, resultSizeEstimate=19.0K, resultSizeActual=9.3K) [left] + │ ║ ║ ║ s: Var (name=asset) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_72f33a14_uri, value=http://example.com/theme/grid/Generator, anonymous) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=9.3K) [right] + │ ║ ║ s: Var (name=asset) + │ ║ ║ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ ║ o: Var (name=substation) + │ ║ ╚══ Extension (resultSizeActual=37.3K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=37.3K) + │ ║ │ s: Var (name=asset) + │ ║ │ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ o: Var (name=substation) + │ ║ └── ExtensionElem (optSub) + │ ║ Var (name=substation) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=asset) + │ ║ GroupElem (assetCount) + │ ║ Count (Distinct) + │ ║ Var (name=asset) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=asset) + └── ExtensionElem (assetCount) + Count (Distinct) + Var (name=asset) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-7.txt new file mode 100644 index 00000000000..731cb627be4 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-7.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=6) + │ ║ ├── Filter (resultSizeActual=6) + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=51.7K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=transformer) + │ ║ │ ║ p: Var (name=_const_fe6c498e_uri, value=http://example.com/theme/grid/hasMeter, anonymous) + │ ║ │ ║ o: Var (name=meter) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=6) + │ ║ │ ├── Filter (resultSizeActual=2) [left] + │ ║ │ │ ╠══ Or + │ ║ │ │ ║ ├── Compare (=) + │ ║ │ │ ║ │ Var (name=name) + │ ║ │ │ ║ │ ValueConstant (value="Substation 0") + │ ║ │ │ ║ └── Compare (=) + │ ║ │ │ ║ Var (name=name) + │ ║ │ │ ║ ValueConstant (value="Substation 1") + │ ║ │ │ ╚══ StatementPattern (costEstimate=4.7K, resultSizeEstimate=9.4K, resultSizeActual=9.3K) + │ ║ │ │ s: Var (name=substation) + │ ║ │ │ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ │ │ o: Var (name=name) + │ ║ │ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=1.50, resultSizeEstimate=3.99, resultSizeActual=8) [left] + │ ║ │ ║ s: Var (name=transformer) + │ ║ │ ║ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ ║ o: Var (name=substation) + │ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.55, resultSizeActual=6) [right] + │ ║ │ s: Var (name=transformer) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_d6ff201a_uri, value=http://example.com/theme/grid/Transformer, anonymous) + │ ║ └── Filter (new scope) (resultSizeActual=0) + │ ║ ╠══ Compare (=) + │ ║ ║ Var (name=load) + │ ║ ║ Var (name=substation) + │ ║ ╚══ StatementPattern (resultSizeEstimate=109.5K, resultSizeActual=112.0K) + │ ║ s: Var (name=meter) + │ ║ p: Var (name=_const_bcd29754_uri, value=http://example.com/theme/grid/measures, anonymous) + │ ║ o: Var (name=load) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=transformer) + └── ExtensionElem (count) + Count (Distinct) + Var (name=transformer) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-8.txt new file mode 100644 index 00000000000..201af991a99 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-8.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "substation" +║ ProjectionElem "transformerCount" +╚══ Extension (resultSizeActual=0) + ├── Extension (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (substation) (resultSizeActual=0) + │ ║ Filter (resultSizeActual=0) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=51.7K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=transformer) + │ ║ │ ║ p: Var (name=_const_fe6c498e_uri, value=http://example.com/theme/grid/hasMeter, anonymous) + │ ║ │ ║ o: Var (name=meter) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optTransformer) + │ ║ │ Var (name=substation) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=9.3K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=9.3K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=7.5K, resultSizeEstimate=15.2K, resultSizeActual=9.3K) [left] + │ ║ ║ │ s: Var (name=substation) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_ac9f03d3_uri, value=http://example.com/theme/grid/Substation, anonymous) + │ ║ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=9.3K) [right] + │ ║ ║ s: Var (name=substation) + │ ║ ║ p: Var (name=_const_9661228a_uri, value=http://example.com/theme/grid/name, anonymous) + │ ║ ║ o: Var (name=name) + │ ║ ╚══ Extension (resultSizeActual=0) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=0.68, resultSizeActual=0) + │ ║ │ s: Var (name=substation) + │ ║ │ p: Var (name=_const_35542676_uri, value=http://example.com/theme/grid/feeds, anonymous) + │ ║ │ o: Var (name=transformer) + │ ║ └── ExtensionElem (optTransformer) + │ ║ Var (name=transformer) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=transformer) + │ ║ GroupElem (transformerCount) + │ ║ Count (Distinct) + │ ║ Var (name=transformer) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=transformer) + └── ExtensionElem (transformerCount) + Count (Distinct) + Var (name=transformer) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-9.txt new file mode 100644 index 00000000000..3afca0f704a --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ELECTRICAL_GRID/query-9.txt @@ -0,0 +1,41 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=0) + │ ║ ├── LeftJoin (LeftJoinIterator) (resultSizeActual=0) + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=0) [left] + │ ║ │ ║ ├── Filter (resultSizeActual=58) [left] + │ ║ │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ │ ║ Var (name=cap) + │ ║ │ ║ │ ║ ValueConstant (value="500"^^) + │ ║ │ ║ │ ║ ValueConstant (value="600"^^) + │ ║ │ ║ │ ║ ValueConstant (value="700"^^) + │ ║ │ ║ │ ╚══ StatementPattern (costEstimate=4.7K, resultSizeEstimate=9.4K, resultSizeActual=9.3K) + │ ║ │ ║ │ s: Var (name=line) + │ ║ │ ║ │ p: Var (name=_const_f300a539_uri, value=http://example.com/theme/grid/capacity, anonymous) + │ ║ │ ║ │ o: Var (name=cap) + │ ║ │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.55, resultSizeActual=0) [right] + │ ║ │ ║ s: Var (name=line) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_9651cc13_uri, value=http://example.com/theme/grid/Line, anonymous) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=2.00) [right] + │ ║ │ s: Var (name=line) + │ ║ │ p: Var (name=_const_342e0de3_uri, value=http://example.com/theme/grid/connectsTo, anonymous) + │ ║ │ o: Var (name=substation) + │ ║ └── Filter (new scope) + │ ║ ╠══ Compare (<) + │ ║ ║ Var (name=cap2) + │ ║ ║ ValueConstant (value="500"^^) + │ ║ ╚══ StatementPattern (resultSizeEstimate=9.4K) + │ ║ s: Var (name=line) + │ ║ p: Var (name=_const_f300a539_uri, value=http://example.com/theme/grid/capacity, anonymous) + │ ║ o: Var (name=cap2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=line) + └── ExtensionElem (count) + Count (Distinct) + Var (name=line) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-0.txt new file mode 100644 index 00000000000..5163b88b9b0 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-0.txt @@ -0,0 +1,33 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=132.6K) + │ ║ ├── Filter (resultSizeActual=132.6K) [left] + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optAssembly) + │ ║ │ ║ Var (name=component) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=132.6K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=132.7K, resultSizeActual=132.6K) [left] + │ ║ │ │ s: Var (name=component) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_347c8ab7_uri, value=http://example.com/theme/engineering/Component, anonymous) + │ ║ │ └── Extension (resultSizeActual=132.6K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=132.6K) + │ ║ │ ║ s: Var (name=component) + │ ║ │ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ │ ║ o: Var (name=assembly) + │ ║ │ ╚══ ExtensionElem (optAssembly) + │ ║ │ Var (name=assembly) + │ ║ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=132.6K) [right] + │ ║ s: Var (name=component) + │ ║ p: Var (name=_const_ce5e09a0_uri, value=http://example.com/theme/engineering/dependsOn, anonymous) + │ ║ o: Var (name=dep) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=component) + └── ExtensionElem (count) + Count (Distinct) + Var (name=component) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-1.txt new file mode 100644 index 00000000000..b1507b1025b --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-1.txt @@ -0,0 +1,41 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=4) + │ ║ ├── Filter (resultSizeActual=4) [left] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ Var (name=target) + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="REQ-1002") + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=266.3K) + │ ║ │ ├── BindingSetAssignment ([[target="REQ-1000"], [target="REQ-1001"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ └── Join (JoinIterator) (resultSizeActual=266.3K) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=382.4K, resultSizeEstimate=130.9K, resultSizeActual=268.2K) [left] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ │ ║ o: Var (name=name) + │ ║ │ ╚══ Union (resultSizeActual=266.3K) [right] + │ ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50, resultSizeActual=1.0K) + │ ║ │ │ s: Var (name=entity) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ │ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50, resultSizeActual=265.3K) + │ ║ │ s: Var (name=entity) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_347c8ab7_uri, value=http://example.com/theme/engineering/Component, anonymous) + │ ║ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=0) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ o: Var (name=assembly) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-10.txt new file mode 100644 index 00000000000..fd525d43563 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-10.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=283) + │ ║ ├── Filter (resultSizeActual=284) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optComponent) + │ ║ │ ║ Var (name=assembly) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=284) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=2) [left] + │ ║ │ │ ╠══ Filter (resultSizeActual=2) [left] + │ ║ │ │ ║ ├── Or + │ ║ │ │ ║ │ ╠══ Compare (=) + │ ║ │ │ ║ │ ║ Var (name=name) + │ ║ │ │ ║ │ ║ ValueConstant (value="Assembly 1") + │ ║ │ │ ║ │ ╚══ Compare (=) + │ ║ │ │ ║ │ Var (name=name) + │ ║ │ │ ║ │ ValueConstant (value="Assembly 2") + │ ║ │ │ ║ └── StatementPattern (costEstimate=66.9K, resultSizeEstimate=133.8K, resultSizeActual=134.1K) + │ ║ │ │ ║ s: Var (name=assembly) + │ ║ │ │ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ │ │ ║ o: Var (name=name) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.61, resultSizeActual=2) [right] + │ ║ │ │ s: Var (name=assembly) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_27ef30ec_uri, value=http://example.com/theme/engineering/Assembly, anonymous) + │ ║ │ └── Extension (resultSizeActual=284) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=140, resultSizeActual=284) + │ ║ │ ║ s: Var (name=component) + │ ║ │ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ │ ║ o: Var (name=assembly) + │ ║ │ ╚══ ExtensionElem (optComponent) + │ ║ │ Var (name=component) + │ ║ └── StatementPattern (new scope) (resultSizeEstimate=520, resultSizeActual=520) + │ ║ s: Var (name=requirement) + │ ║ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ o: Var (name=component) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=assembly) + └── ExtensionElem (count) + Count (Distinct) + Var (name=assembly) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-2.txt new file mode 100644 index 00000000000..1ebf2aefca1 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-2.txt @@ -0,0 +1,44 @@ +Projection (resultSizeActual=3) +╠══ ProjectionElemList +║ ProjectionElem "assembly" +║ ProjectionElem "componentCount" +╚══ Extension (resultSizeActual=3) + ├── Extension (resultSizeActual=3) + │ ╠══ Filter (resultSizeActual=3) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (assembly) (resultSizeActual=3) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=420) + │ ║ ├── Join (JoinIterator) (resultSizeActual=3) [left] + │ ║ │ ╠══ Filter (resultSizeActual=3) [left] + │ ║ │ ║ ├── ListMemberOperator + │ ║ │ ║ │ Var (name=assemblyName) + │ ║ │ ║ │ ValueConstant (value="Assembly 1") + │ ║ │ ║ │ ValueConstant (value="Assembly 2") + │ ║ │ ║ │ ValueConstant (value="Assembly 3") + │ ║ │ ║ └── StatementPattern (costEstimate=66.1K, resultSizeEstimate=132.1K, resultSizeActual=134.1K) + │ ║ │ ║ s: Var (name=assembly) + │ ║ │ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ │ ║ o: Var (name=assemblyName) + │ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50, resultSizeActual=3) [right] + │ ║ │ s: Var (name=assembly) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_27ef30ec_uri, value=http://example.com/theme/engineering/Assembly, anonymous) + │ ║ └── StatementPattern (resultSizeEstimate=140, resultSizeActual=420) [right] + │ ║ s: Var (name=component) + │ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ o: Var (name=assembly) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=component) + │ ║ GroupElem (componentCount) + │ ║ Count (Distinct) + │ ║ Var (name=component) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=component) + └── ExtensionElem (componentCount) + Count (Distinct) + Var (name=component) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-3.txt new file mode 100644 index 00000000000..bcdb15ed3aa --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-3.txt @@ -0,0 +1,43 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=1.0K) + │ ║ ├── Filter (resultSizeActual=1.5K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optTest) + │ ║ │ ║ Var (name=requirement) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=1.5K) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=520) [left] + │ ║ │ │ ╠══ StatementPattern (costEstimate=256, resultSizeEstimate=511, resultSizeActual=520) [left] + │ ║ │ │ ║ s: Var (name=requirement) + │ ║ │ │ ║ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ │ │ ║ o: Var (name=component) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50, resultSizeActual=520) [right] + │ ║ │ │ s: Var (name=requirement) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ │ └── Extension (resultSizeActual=1.5K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=2.99, resultSizeActual=1.5K) + │ ║ │ ║ s: Var (name=requirement) + │ ║ │ ║ p: Var (name=_const_c08202a5_uri, value=http://example.com/theme/engineering/verifiedBy, anonymous) + │ ║ │ ║ o: Var (name=test) + │ ║ │ ╚══ ExtensionElem (optTest) + │ ║ │ Var (name=test) + │ ║ └── Filter (new scope) (resultSizeActual=43.7K) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── Str + │ ║ ║ │ Var (name=name) + │ ║ ║ └── ValueConstant (value="Component 1") + │ ║ ╚══ StatementPattern (resultSizeEstimate=133.7K, resultSizeActual=134.1K) + │ ║ s: Var (name=component) + │ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=requirement) + └── ExtensionElem (count) + Count (Distinct) + Var (name=requirement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-4.txt new file mode 100644 index 00000000000..d51a39fdca7 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-4.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=2) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=1.1K, resultSizeActual=0) + │ ║ │ s: Var (name=component) + │ ║ │ p: Var (name=_const_ce5e09a0_uri, value=http://example.com/theme/engineering/dependsOn, anonymous) + │ ║ │ o: Var (name=dep) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=2) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=2) [left] + │ ║ ║ ├── StatementPattern (costEstimate=66.3K, resultSizeEstimate=132.7K, resultSizeActual=132.6K) [left] + │ ║ ║ │ s: Var (name=component) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_347c8ab7_uri, value=http://example.com/theme/engineering/Component, anonymous) + │ ║ ║ └── Filter (resultSizeActual=2) [right] + │ ║ ║ ╠══ Or + │ ║ ║ ║ ├── Compare (=) + │ ║ ║ ║ │ Var (name=name) + │ ║ ║ ║ │ ValueConstant (value="Component 1") + │ ║ ║ ║ └── Compare (=) + │ ║ ║ ║ Var (name=name) + │ ║ ║ ║ ValueConstant (value="Component 2") + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=132.6K) + │ ║ ║ s: Var (name=component) + │ ║ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ ║ o: Var (name=name) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=2) [right] + │ ║ s: Var (name=component) + │ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ o: Var (name=assembly) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=component) + └── ExtensionElem (count) + Count (Distinct) + Var (name=component) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-5.txt new file mode 100644 index 00000000000..56f91acc754 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-5.txt @@ -0,0 +1,39 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ Filter + │ ║ │ ╠══ Compare (<) + │ ║ │ ║ Var (name=value2) + │ ║ │ ║ Var (name=threshold) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=1.6K) + │ ║ │ s: Var (name=measurement) + │ ║ │ p: Var (name=_const_f682b725_uri, value=http://example.com/theme/engineering/measuredValue, anonymous) + │ ║ │ o: Var (name=value2) + │ ║ └── Join (JoinIterator) (resultSizeActual=0) + │ ║ ╠══ BindingSetAssignment ([[threshold="0.85"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=0) [right] + │ ║ ├── Filter (resultSizeActual=0) [left] + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=value) + │ ║ │ ║ ValueConstant (value="0.9"^^) + │ ║ │ ║ ValueConstant (value="0.95"^^) + │ ║ │ ╚══ StatementPattern (costEstimate=4.7K, resultSizeEstimate=1.6K, resultSizeActual=1.5K) + │ ║ │ s: Var (name=measurement) + │ ║ │ p: Var (name=_const_f682b725_uri, value=http://example.com/theme/engineering/measuredValue, anonymous) + │ ║ │ o: Var (name=value) + │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50) [right] + │ ║ s: Var (name=measurement) + │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ o: Var (name=_const_d63bc2f6_uri, value=http://example.com/theme/engineering/Measurement, anonymous) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=measurement) + └── ExtensionElem (count) + Count (Distinct) + Var (name=measurement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-6.txt new file mode 100644 index 00000000000..2fec229e876 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-6.txt @@ -0,0 +1,50 @@ +Projection (resultSizeActual=520) +╠══ ProjectionElemList +║ ProjectionElem "component" +║ ProjectionElem "reqCount" +╚══ Extension (resultSizeActual=520) + ├── Extension (resultSizeActual=520) + │ ╠══ Filter (resultSizeActual=520) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (component) (resultSizeActual=132.6K) + │ ║ Filter (resultSizeActual=133.1K) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optDep) + │ ║ │ Var (name=component) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=133.1K) + │ ║ ╠══ Union (resultSizeActual=133.1K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=520) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=261, resultSizeEstimate=518, resultSizeActual=520) [left] + │ ║ ║ │ ║ s: Var (name=requirement) + │ ║ ║ │ ║ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ ║ │ ║ o: Var (name=component) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.50, resultSizeActual=520) [right] + │ ║ ║ │ s: Var (name=requirement) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=132.7K, resultSizeActual=132.6K) + │ ║ ║ s: Var (name=component) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_347c8ab7_uri, value=http://example.com/theme/engineering/Component, anonymous) + │ ║ ╚══ Extension (resultSizeActual=133.1K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=133.1K) + │ ║ │ s: Var (name=component) + │ ║ │ p: Var (name=_const_ce5e09a0_uri, value=http://example.com/theme/engineering/dependsOn, anonymous) + │ ║ │ o: Var (name=dep) + │ ║ └── ExtensionElem (optDep) + │ ║ Var (name=dep) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=requirement) + │ ║ GroupElem (reqCount) + │ ║ Count (Distinct) + │ ║ Var (name=requirement) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=requirement) + └── ExtensionElem (reqCount) + Count (Distinct) + Var (name=requirement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-7.txt new file mode 100644 index 00000000000..a3268c4fe26 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-7.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=0) + │ ║ ├── Filter (resultSizeActual=2) + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=519, resultSizeActual=0) + │ ║ │ ║ s: Var (name=requirement) + │ ║ │ ║ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ │ ║ o: Var (name=component) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=2) + │ ║ │ ├── StatementPattern (costEstimate=4.7K, resultSizeEstimate=9.1K, resultSizeActual=520) [left] + │ ║ │ │ s: Var (name=requirement) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ │ └── Filter (resultSizeActual=2) [right] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ ValueConstant (value="REQ-1000") + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="REQ-1001") + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=520) + │ ║ │ s: Var (name=requirement) + │ ║ │ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ │ o: Var (name=name) + │ ║ └── Join (JoinIterator) (resultSizeActual=1.5K) + │ ║ ╠══ StatementPattern (costEstimate=7.4M, resultSizeEstimate=3.0K, resultSizeActual=3.1K) [left] + │ ║ ║ s: Var (name=requirement) + │ ║ ║ p: Var (name=_const_c08202a5_uri, value=http://example.com/theme/engineering/verifiedBy, anonymous) + │ ║ ║ o: Var (name=test) + │ ║ ╚══ StatementPattern (costEstimate=2.40, resultSizeEstimate=0.51, resultSizeActual=1.5K) [right] + │ ║ s: Var (name=test) + │ ║ p: Var (name=_const_c08202a5_uri, value=http://example.com/theme/engineering/verifiedBy, anonymous) + │ ║ o: Var (name=measurement) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=requirement) + └── ExtensionElem (count) + Count (Distinct) + Var (name=requirement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-8.txt new file mode 100644 index 00000000000..5824f5a9826 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-8.txt @@ -0,0 +1,55 @@ +Projection (resultSizeActual=520) +╠══ ProjectionElemList +║ ProjectionElem "component" +║ ProjectionElem "reqCount" +╚══ Extension (resultSizeActual=520) + ├── Extension (resultSizeActual=520) + │ ╠══ Filter (resultSizeActual=520) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="1"^^) + │ ║ └── Group (component) (resultSizeActual=520) + │ ║ Filter (resultSizeActual=520) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optDep) + │ ║ │ Var (name=component) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=520) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=520) [left] + │ ║ ║ ├── StatementPattern (costEstimate=131, resultSizeEstimate=519, resultSizeActual=520) [left] + │ ║ ║ │ s: Var (name=requirement) + │ ║ ║ │ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ ║ │ o: Var (name=component) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=520) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.59, resultSizeActual=520) [left] + │ ║ ║ ║ s: Var (name=requirement) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=520) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.59, resultSizeActual=520) [left] + │ ║ ║ │ s: Var (name=component) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_347c8ab7_uri, value=http://example.com/theme/engineering/Component, anonymous) + │ ║ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=520) [right] + │ ║ ║ s: Var (name=component) + │ ║ ║ p: Var (name=_const_b1044d90_uri, value=http://example.com/theme/engineering/partOf, anonymous) + │ ║ ║ o: Var (name=assembly) + │ ║ ╚══ Extension (resultSizeActual=520) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=520) + │ ║ │ s: Var (name=component) + │ ║ │ p: Var (name=_const_ce5e09a0_uri, value=http://example.com/theme/engineering/dependsOn, anonymous) + │ ║ │ o: Var (name=dep) + │ ║ └── ExtensionElem (optDep) + │ ║ Var (name=dep) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=requirement) + │ ║ GroupElem (reqCount) + │ ║ Count (Distinct) + │ ║ Var (name=requirement) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=requirement) + └── ExtensionElem (reqCount) + Count (Distinct) + Var (name=requirement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-9.txt new file mode 100644 index 00000000000..c847dda25eb --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/ENGINEERING/query-9.txt @@ -0,0 +1,54 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── And + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optName) + │ ║ │ ║ ValueConstant (value="") + │ ║ │ ╚══ Exists + │ ║ │ StatementPattern (resultSizeEstimate=520) + │ ║ │ s: Var (name=requirement) + │ ║ │ p: Var (name=_const_b98f621b_uri, value=http://example.com/theme/engineering/satisfies, anonymous) + │ ║ │ o: Var (name=component) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=0) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=0) [left] + │ ║ ║ ├── BindingSetAssignment ([[threshold="0.85"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=0) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=0) [left] + │ ║ ║ ║ ├── ListMemberOperator + │ ║ ║ ║ │ Var (name=value) + │ ║ ║ ║ │ ValueConstant (value="0.85"^^) + │ ║ ║ ║ │ ValueConstant (value="0.9"^^) + │ ║ ║ ║ │ ValueConstant (value="0.95"^^) + │ ║ ║ ║ └── StatementPattern (costEstimate=4.7K, resultSizeEstimate=1.6K, resultSizeActual=1.5K) + │ ║ ║ ║ s: Var (name=measurement) + │ ║ ║ ║ p: Var (name=_const_f682b725_uri, value=http://example.com/theme/engineering/measuredValue, anonymous) + │ ║ ║ ║ o: Var (name=value) + │ ║ ║ ╚══ Join (JoinIterator) [right] + │ ║ ║ ├── StatementPattern (costEstimate=28, resultSizeEstimate=3.1K) [left] + │ ║ ║ │ s: Var (name=test) + │ ║ ║ │ p: Var (name=_const_c08202a5_uri, value=http://example.com/theme/engineering/verifiedBy, anonymous) + │ ║ ║ │ o: Var (name=measurement) + │ ║ ║ └── Join (JoinIterator) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=28, resultSizeEstimate=3.1K) [left] + │ ║ ║ ║ s: Var (name=requirement) + │ ║ ║ ║ p: Var (name=_const_c08202a5_uri, value=http://example.com/theme/engineering/verifiedBy, anonymous) + │ ║ ║ ║ o: Var (name=test) + │ ║ ║ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.61) [right] + │ ║ ║ s: Var (name=requirement) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_57f1c37d_uri, value=http://example.com/theme/engineering/Requirement, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=133.8K) [right] + │ ║ s: Var (name=component) + │ ║ p: Var (name=_const_b8416c71_uri, value=http://example.com/theme/engineering/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=requirement) + └── ExtensionElem (count) + Count (Distinct) + Var (name=requirement) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-0.txt new file mode 100644 index 00000000000..e8325a592f1 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-0.txt @@ -0,0 +1,33 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=1.5M) + │ ║ ├── Filter (resultSizeActual=267.2K) [left] + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optNeighbor) + │ ║ │ ║ Var (name=node) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=267.2K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=40.3K, resultSizeActual=40.2K) [left] + │ ║ │ │ s: Var (name=node) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ │ └── Extension (resultSizeActual=267.2K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=6.64, resultSizeActual=267.2K) + │ ║ │ ║ s: Var (name=node) + │ ║ │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ │ ║ o: Var (name=neighbor) + │ ║ │ ╚══ ExtensionElem (optNeighbor) + │ ║ │ Var (name=neighbor) + │ ║ └── StatementPattern (resultSizeEstimate=5.86, resultSizeActual=1.5M) [right] + │ ║ s: Var (name=node) + │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ o: Var (name=w) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-1.txt new file mode 100644 index 00000000000..e65e004deec --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-1.txt @@ -0,0 +1,41 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=716.1K) + │ ║ ├── Or + │ ║ │ ╠══ Compare (=) + │ ║ │ ║ Var (name=w) + │ ║ │ ║ Var (name=target) + │ ║ │ ╚══ Compare (=) + │ ║ │ Var (name=w) + │ ║ │ ValueConstant (value="3"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=3.5M) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=615.0K) [left] + │ ║ ║ ├── BindingSetAssignment ([[target="1"^^], [target="2"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ ║ └── Union (resultSizeActual=615.0K) [right] + │ ║ ║ ╠══ Join (JoinIterator) (resultSizeActual=534.5K) + │ ║ ║ ║ ├── StatementPattern (costEstimate=120.8K, resultSizeEstimate=40.3K, resultSizeActual=80.5K) [left] + │ ║ ║ ║ │ s: Var (name=entity) + │ ║ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ ║ ║ └── StatementPattern (costEstimate=3.41, resultSizeEstimate=6.64, resultSizeActual=534.5K) [right] + │ ║ ║ ║ s: Var (name=entity) + │ ║ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ ║ o: Var (name=targetNode) + │ ║ ║ ╚══ StatementPattern (new scope) (costEstimate=4861.6M, resultSizeEstimate=40.3K, resultSizeActual=80.5K) + │ ║ ║ s: Var (name=entity) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=5.86, resultSizeActual=3.5M) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ o: Var (name=w) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-10.txt new file mode 100644 index 00000000000..518ff143703 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-10.txt @@ -0,0 +1,54 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=95) + │ ║ ├── Filter (resultSizeActual=95) + │ ║ │ ╠══ Not + │ ║ │ ║ Exists + │ ║ │ ║ Filter (resultSizeActual=0) + │ ║ │ ║ ├── Compare (<) + │ ║ │ ║ │ Var (name=w2) + │ ║ │ ║ │ Var (name=threshold) + │ ║ │ ║ └── Join (JoinIterator) (resultSizeActual=511.1K) + │ ║ │ ║ ╠══ StatementPattern (costEstimate=16110.2M, resultSizeEstimate=266.8K, resultSizeActual=115.3K) [left] + │ ║ │ ║ ║ s: Var (name=node) + │ ║ │ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ │ ║ ║ o: Var (name=n2) + │ ║ │ ║ ╚══ StatementPattern (costEstimate=3.19, resultSizeEstimate=5.17, resultSizeActual=511.1K) [right] + │ ║ │ ║ s: Var (name=n2) + │ ║ │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ ║ o: Var (name=w2) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=89.1K) + │ ║ │ ├── BindingSetAssignment ([[threshold="3"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ │ └── Join (JoinIterator) (resultSizeActual=89.1K) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=120.8K, resultSizeEstimate=40.3K, resultSizeActual=40.2K) [left] + │ ║ │ ║ s: Var (name=node) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ │ ╚══ Filter (resultSizeActual=89.1K) [right] + │ ║ │ ├── ListMemberOperator + │ ║ │ │ Var (name=w) + │ ║ │ │ ValueConstant (value="1"^^) + │ ║ │ │ ValueConstant (value="2"^^) + │ ║ │ │ ValueConstant (value="3"^^) + │ ║ │ │ ValueConstant (value="4"^^) + │ ║ │ └── StatementPattern (costEstimate=3.19, resultSizeEstimate=5.17, resultSizeActual=222.7K) + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ o: Var (name=w) + │ ║ └── Extension (resultSizeActual=0) + │ ║ ╠══ StatementPattern (resultSizeEstimate=266.8K, resultSizeActual=0) + │ ║ ║ s: Var (name=node) + │ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ o: Var (name=node) + │ ║ ╚══ ExtensionElem (_anon_path_1) + │ ║ Var (name=node) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-2.txt new file mode 100644 index 00000000000..e3a8a163faa --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-2.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=36.7K) +╠══ ProjectionElemList +║ ProjectionElem "node" +║ ProjectionElem "neighborCount" +╚══ Extension (resultSizeActual=36.7K) + ├── Extension (resultSizeActual=36.7K) + │ ╠══ Filter (resultSizeActual=36.7K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (node) (resultSizeActual=36.7K) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=470.5K) + │ ║ ├── Join (JoinIterator) (resultSizeActual=470.5K) [left] + │ ║ │ ╠══ Filter (resultSizeActual=66.8K) [left] + │ ║ │ ║ ├── ListMemberOperator + │ ║ │ ║ │ Var (name=w) + │ ║ │ ║ │ ValueConstant (value="1"^^) + │ ║ │ ║ │ ValueConstant (value="2"^^) + │ ║ │ ║ │ ValueConstant (value="3"^^) + │ ║ │ ║ └── StatementPattern (costEstimate=72.2K, resultSizeEstimate=216.6K, resultSizeActual=222.7K) + │ ║ │ ║ s: Var (name=node) + │ ║ │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ ║ o: Var (name=w) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=470.5K) [right] + │ ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=66.8K) [left] + │ ║ │ │ s: Var (name=node) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ │ └── StatementPattern (costEstimate=3.29, resultSizeEstimate=6.77, resultSizeActual=470.5K) [right] + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ │ o: Var (name=neighbor) + │ ║ └── StatementPattern (resultSizeEstimate=0.00, resultSizeActual=66) [right] + │ ║ s: Var (name=neighbor) + │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ o: Var (name=node) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=neighbor) + │ ║ GroupElem (neighborCount) + │ ║ Count (Distinct) + │ ║ Var (name=neighbor) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=neighbor) + └── ExtensionElem (neighborCount) + Count (Distinct) + Var (name=neighbor) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-3.txt new file mode 100644 index 00000000000..040ef955360 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-3.txt @@ -0,0 +1,37 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=111.5K) + │ ║ ├── Filter (resultSizeActual=111.5K) + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optWeight) + │ ║ │ ║ ValueConstant (value="5"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=222.7K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=40.3K, resultSizeActual=40.2K) [left] + │ ║ │ │ s: Var (name=node) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ │ └── Extension (resultSizeActual=222.7K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=5.84, resultSizeActual=222.7K) + │ ║ │ ║ s: Var (name=node) + │ ║ │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ ║ o: Var (name=w) + │ ║ │ ╚══ ExtensionElem (optWeight) + │ ║ │ Var (name=w) + │ ║ └── Filter (new scope) (resultSizeActual=0) + │ ║ ╠══ Compare (=) + │ ║ ║ Var (name=neighbor) + │ ║ ║ Var (name=node) + │ ║ ╚══ StatementPattern (resultSizeEstimate=255.3K, resultSizeActual=267.2K) + │ ║ s: Var (name=node) + │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ o: Var (name=neighbor) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-4.txt new file mode 100644 index 00000000000..cf061cb2621 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-4.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=1.0K) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=266.2K, resultSizeActual=0) + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ │ o: Var (name=neighbor) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=238.7K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=44.6K) [left] + │ ║ ║ ├── Filter (resultSizeActual=44.6K) [left] + │ ║ ║ │ ╠══ Or + │ ║ ║ │ ║ ├── Compare (=) + │ ║ ║ │ ║ │ Var (name=w) + │ ║ ║ │ ║ │ ValueConstant (value="1"^^) + │ ║ ║ │ ║ └── Compare (=) + │ ║ ║ │ ║ Var (name=w) + │ ║ ║ │ ║ ValueConstant (value="2"^^) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=108.8K, resultSizeEstimate=217.5K, resultSizeActual=222.7K) + │ ║ ║ │ s: Var (name=node) + │ ║ ║ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ ║ │ o: Var (name=w) + │ ║ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=44.6K) [right] + │ ║ ║ s: Var (name=node) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=5.33, resultSizeActual=237.7K) [right] + │ ║ s: Var (name=neighbor) + │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ o: Var (name=node) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-5.txt new file mode 100644 index 00000000000..bc243e23fdc --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-5.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=6.0K) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ╠══ Compare (<) + │ ║ │ ║ Var (name=w2) + │ ║ │ ║ Var (name=threshold) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=221.3K, resultSizeActual=172.5K) + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ o: Var (name=w2) + │ ║ └── Join (JoinIterator) (resultSizeActual=66.7K) + │ ║ ╠══ BindingSetAssignment ([[threshold="4"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=66.7K) [right] + │ ║ ├── StatementPattern (costEstimate=120.8K, resultSizeEstimate=40.3K, resultSizeActual=40.2K) [left] + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ └── Filter (resultSizeActual=66.7K) [right] + │ ║ ╠══ ListMemberOperator + │ ║ ║ Var (name=w) + │ ║ ║ ValueConstant (value="4"^^) + │ ║ ║ ValueConstant (value="5"^^) + │ ║ ║ ValueConstant (value="6"^^) + │ ║ ╚══ StatementPattern (costEstimate=3.28, resultSizeEstimate=5.21, resultSizeActual=222.7K) + │ ║ s: Var (name=node) + │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ o: Var (name=w) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-6.txt new file mode 100644 index 00000000000..d964c3da557 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-6.txt @@ -0,0 +1,50 @@ +Projection (resultSizeActual=40.2K) +╠══ ProjectionElemList +║ ProjectionElem "node" +║ ProjectionElem "neighborCount" +╚══ Extension (resultSizeActual=40.2K) + ├── Extension (resultSizeActual=40.2K) + │ ╠══ Filter (resultSizeActual=40.2K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (node) (resultSizeActual=40.2K) + │ ║ Filter (resultSizeActual=2.9M) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optWeight) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=2.9M) + │ ║ ╠══ Union (resultSizeActual=534.5K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=267.2K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=20.1K, resultSizeEstimate=40.3K, resultSizeActual=40.2K) [left] + │ ║ ║ │ ║ s: Var (name=node) + │ ║ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ ║ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=3.43, resultSizeEstimate=6.78, resultSizeActual=267.2K) [right] + │ ║ ║ │ s: Var (name=node) + │ ║ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ │ o: Var (name=neighbor) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=266.2K, resultSizeActual=267.2K) + │ ║ ║ s: Var (name=neighbor) + │ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ o: Var (name=node) + │ ║ ╚══ Extension (resultSizeActual=2.9M) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=5.09, resultSizeActual=2.9M) + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ o: Var (name=w) + │ ║ └── ExtensionElem (optWeight) + │ ║ Var (name=w) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=neighbor) + │ ║ GroupElem (neighborCount) + │ ║ Count (Distinct) + │ ║ Var (name=neighbor) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=neighbor) + └── ExtensionElem (neighborCount) + Count (Distinct) + Var (name=neighbor) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-7.txt new file mode 100644 index 00000000000..a6b720b442f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-7.txt @@ -0,0 +1,44 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=44.7K) + │ ║ ├── Filter (resultSizeActual=44.7K) + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=266.3K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=node) + │ ║ │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ │ ║ o: Var (name=neighbor) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=44.7K) + │ ║ │ ├── Filter (resultSizeActual=44.7K) [left] + │ ║ │ │ ╠══ Or + │ ║ │ │ ║ ├── Compare (=) + │ ║ │ │ ║ │ Var (name=w) + │ ║ │ │ ║ │ ValueConstant (value="8"^^) + │ ║ │ │ ║ └── Compare (=) + │ ║ │ │ ║ Var (name=w) + │ ║ │ │ ║ ValueConstant (value="9"^^) + │ ║ │ │ ╚══ StatementPattern (costEstimate=110.7K, resultSizeEstimate=221.3K, resultSizeActual=222.7K) + │ ║ │ │ s: Var (name=node) + │ ║ │ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ │ o: Var (name=w) + │ ║ │ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=44.7K) [right] + │ ║ │ s: Var (name=node) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ └── Filter (new scope) (resultSizeActual=0) + │ ║ ╠══ Compare (=) + │ ║ ║ Var (name=neighbor) + │ ║ ║ Var (name=node) + │ ║ ╚══ StatementPattern (resultSizeEstimate=266.3K, resultSizeActual=267.2K) + │ ║ s: Var (name=neighbor) + │ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ o: Var (name=node) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-8.txt new file mode 100644 index 00000000000..e4fb5877296 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-8.txt @@ -0,0 +1,43 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=432) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optWeight) + │ ║ │ ValueConstant (value="7"^^) + │ ║ │ ValueConstant (value="8"^^) + │ ║ │ ValueConstant (value="9"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=1.6K) + │ ║ ╠══ Filter (resultSizeActual=294) [left] + │ ║ ║ ├── Exists + │ ║ ║ │ StatementPattern (resultSizeEstimate=266.7K, resultSizeActual=0) + │ ║ ║ │ s: Var (name=end) + │ ║ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ │ o: Var (name=node) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=1.7M) + │ ║ ║ ╠══ StatementPattern (costEstimate=133.4K, resultSizeEstimate=266.7K, resultSizeActual=267.2K) [left] + │ ║ ║ ║ s: Var (name=mid) + │ ║ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ ║ o: Var (name=end) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=1.7M) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.61, resultSizeEstimate=5.69, resultSizeActual=1.7M) [left] + │ ║ ║ │ s: Var (name=node) + │ ║ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ │ o: Var (name=mid) + │ ║ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=1.7M) [right] + │ ║ ║ s: Var (name=node) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_b000c52_uri, value=http://example.com/theme/connected/Node, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=5.13, resultSizeActual=1.6K) [right] + │ ║ s: Var (name=node) + │ ║ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ o: Var (name=optWeight) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=node) + └── ExtensionElem (count) + Count (Distinct) + Var (name=node) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-9.txt new file mode 100644 index 00000000000..206a0c8801c --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/HIGHLY_CONNECTED/query-9.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=40.2K) +╠══ ProjectionElemList +║ ProjectionElem "node" +║ ProjectionElem "degree" +╚══ Extension (resultSizeActual=40.2K) + ├── Extension (resultSizeActual=40.2K) + │ ╠══ Filter (resultSizeActual=40.2K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="1"^^) + │ ║ └── Group (node) (resultSizeActual=40.2K) + │ ║ Filter (resultSizeActual=2.9M) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optWeight) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=2.9M) + │ ║ ╠══ Union (resultSizeActual=534.5K) [left] + │ ║ ║ ├── StatementPattern (new scope) (resultSizeEstimate=266.8K, resultSizeActual=267.2K) + │ ║ ║ │ s: Var (name=node) + │ ║ ║ │ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ │ o: Var (name=neighbor) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=266.8K, resultSizeActual=267.2K) + │ ║ ║ s: Var (name=neighbor) + │ ║ ║ p: Var (name=_const_2e732754_uri, value=http://example.com/theme/connected/connectsTo, anonymous) + │ ║ ║ o: Var (name=node) + │ ║ ╚══ Extension (resultSizeActual=2.9M) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=5.14, resultSizeActual=2.9M) + │ ║ │ s: Var (name=neighbor) + │ ║ │ p: Var (name=_const_909a60a8_uri, value=http://example.com/theme/connected/weight, anonymous) + │ ║ │ o: Var (name=w) + │ ║ └── ExtensionElem (optWeight) + │ ║ Var (name=w) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=neighbor) + │ ║ GroupElem (degree) + │ ║ Count (Distinct) + │ ║ Var (name=neighbor) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=neighbor) + └── ExtensionElem (degree) + Count (Distinct) + Var (name=neighbor) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-0.txt new file mode 100644 index 00000000000..ae077edd8ea --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-0.txt @@ -0,0 +1,38 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=770.9K) + │ ║ ├── Filter (resultSizeActual=386.3K) [left] + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optBranch) + │ ║ │ ║ Var (name=book) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=386.3K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=128.9K, resultSizeActual=128.8K) [left] + │ ║ │ │ s: Var (name=book) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_6cec5947_uri, value=http://example.com/theme/library/Book, anonymous) + │ ║ │ └── Extension (resultSizeActual=386.3K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=386.3K) + │ ║ │ ║ ├── StatementPattern (costEstimate=1.49, resultSizeEstimate=3.13, resultSizeActual=386.3K) [left] + │ ║ │ ║ │ s: Var (name=book) + │ ║ │ ║ │ p: Var (name=_const_469a1e31_uri, value=http://example.com/theme/library/hasCopy, anonymous) + │ ║ │ ║ │ o: Var (name=copy) + │ ║ │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=386.3K) [right] + │ ║ │ ║ s: Var (name=copy) + │ ║ │ ║ p: Var (name=_const_ecfc63a7_uri, value=http://example.com/theme/library/locatedAt, anonymous) + │ ║ │ ║ o: Var (name=branch) + │ ║ │ ╚══ ExtensionElem (optBranch) + │ ║ │ Var (name=branch) + │ ║ └── StatementPattern (resultSizeEstimate=2.00, resultSizeActual=770.9K) [right] + │ ║ s: Var (name=book) + │ ║ p: Var (name=_const_e1624c50_uri, value=http://example.com/theme/library/writtenBy, anonymous) + │ ║ o: Var (name=author) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=book) + └── ExtensionElem (count) + Count (Distinct) + Var (name=book) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-1.txt new file mode 100644 index 00000000000..c11fdd667e7 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-1.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=4) + │ ║ ├── Filter (resultSizeActual=4) [left] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ Var (name=target) + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="Member 3") + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=267.8K) + │ ║ │ ├── BindingSetAssignment ([[target="Member 1"], [target="Member 2"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ └── Union (resultSizeActual=267.8K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=10.1K) + │ ║ │ ║ ├── StatementPattern (costEstimate=129.6K, resultSizeEstimate=43.2K, resultSizeActual=90.6K) [left] + │ ║ │ ║ │ s: Var (name=entity) + │ ║ │ ║ │ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ │ ║ │ o: Var (name=name) + │ ║ │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.77, resultSizeActual=10.1K) [right] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_f5728978_uri, value=http://example.com/theme/library/Member, anonymous) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=257.7K) + │ ║ │ ├── StatementPattern (costEstimate=8412.8M, resultSizeEstimate=122.5K, resultSizeActual=257.7K) [left] + │ ║ │ │ s: Var (name=entity) + │ ║ │ │ p: Var (name=_const_335cbfda_uri, value=http://example.com/theme/library/title, anonymous) + │ ║ │ │ o: Var (name=name) + │ ║ │ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.77, resultSizeActual=257.7K) [right] + │ ║ │ s: Var (name=entity) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_6cec5947_uri, value=http://example.com/theme/library/Book, anonymous) + │ ║ └── StatementPattern (resultSizeEstimate=3.07, resultSizeActual=0) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_469a1e31_uri, value=http://example.com/theme/library/hasCopy, anonymous) + │ ║ o: Var (name=copy) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-10.txt new file mode 100644 index 00000000000..e776d924e2f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-10.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=618.4K) + │ ║ ├── Filter (resultSizeActual=772.6K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optCopy) + │ ║ │ ║ Var (name=branch) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=772.6K) + │ ║ │ ├── Union (resultSizeActual=10) [left] + │ ║ │ │ ╠══ StatementPattern (new scope) (resultSizeEstimate=96.8K, resultSizeActual=5) + │ ║ │ │ ║ s: Var (name=branch) + │ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ o: Var (name=_const_e35f2480_uri, value=http://example.com/theme/library/Branch, anonymous) + │ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=5) + │ ║ │ │ ├── StatementPattern (costEstimate=22.7K, resultSizeEstimate=45.3K, resultSizeActual=45.3K) [left] + │ ║ │ │ │ s: Var (name=branch) + │ ║ │ │ │ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ │ │ │ o: Var (name=name) + │ ║ │ │ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.82, resultSizeActual=5) [right] + │ ║ │ │ s: Var (name=branch) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_e35f2480_uri, value=http://example.com/theme/library/Branch, anonymous) + │ ║ │ └── Extension (resultSizeActual=772.6K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=73.6K, resultSizeActual=772.6K) + │ ║ │ ║ s: Var (name=copy) + │ ║ │ ║ p: Var (name=_const_ecfc63a7_uri, value=http://example.com/theme/library/locatedAt, anonymous) + │ ║ │ ║ o: Var (name=branch) + │ ║ │ ╚══ ExtensionElem (optCopy) + │ ║ │ Var (name=copy) + │ ║ └── Filter (new scope) (resultSizeActual=1) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=name2) + │ ║ ║ └── ValueConstant (value="branch 0") + │ ║ ╚══ StatementPattern (resultSizeEstimate=45.3K, resultSizeActual=45.3K) + │ ║ s: Var (name=branch) + │ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ o: Var (name=name2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=branch) + └── ExtensionElem (count) + Count (Distinct) + Var (name=branch) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-2.txt new file mode 100644 index 00000000000..db10a3cd214 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-2.txt @@ -0,0 +1,44 @@ +Projection (resultSizeActual=3) +╠══ ProjectionElemList +║ ProjectionElem "author" +║ ProjectionElem "bookCount" +╚══ Extension (resultSizeActual=3) + ├── Extension (resultSizeActual=3) + │ ╠══ Filter (resultSizeActual=3) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (author) (resultSizeActual=3) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=15) + │ ║ ├── Join (JoinIterator) (resultSizeActual=3) [left] + │ ║ │ ╠══ Filter (resultSizeActual=3) [left] + │ ║ │ ║ ├── ListMemberOperator + │ ║ │ ║ │ Var (name=authorName) + │ ║ │ ║ │ ValueConstant (value="Author 1") + │ ║ │ ║ │ ValueConstant (value="Author 2") + │ ║ │ ║ │ ValueConstant (value="Author 3") + │ ║ │ ║ └── StatementPattern (costEstimate=22.4K, resultSizeEstimate=44.8K, resultSizeActual=45.3K) + │ ║ │ ║ s: Var (name=author) + │ ║ │ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ │ ║ o: Var (name=authorName) + │ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.77, resultSizeActual=3) [right] + │ ║ │ s: Var (name=author) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_e1dd2069_uri, value=http://example.com/theme/library/Author, anonymous) + │ ║ └── StatementPattern (resultSizeEstimate=5.00, resultSizeActual=15) [right] + │ ║ s: Var (name=book) + │ ║ p: Var (name=_const_e1624c50_uri, value=http://example.com/theme/library/writtenBy, anonymous) + │ ║ o: Var (name=author) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=book) + │ ║ GroupElem (bookCount) + │ ║ Count (Distinct) + │ ║ Var (name=book) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=book) + └── ExtensionElem (bookCount) + Count (Distinct) + Var (name=book) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-3.txt new file mode 100644 index 00000000000..09ca59fcffd --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-3.txt @@ -0,0 +1,44 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=7.9K) + │ ║ ├── Filter (resultSizeActual=10.1K) + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optDue) + │ ║ │ ║ ValueConstant (value="2024-01-10"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=10.1K) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=10.1K) [left] + │ ║ │ │ ╠══ StatementPattern (costEstimate=4.9K, resultSizeEstimate=10.1K, resultSizeActual=10.1K) [left] + │ ║ │ │ ║ s: Var (name=loan) + │ ║ │ │ ║ p: Var (name=_const_b9a39489_uri, value=http://example.com/theme/library/borrowedBy, anonymous) + │ ║ │ │ ║ o: Var (name=member) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.81, resultSizeActual=10.1K) [right] + │ ║ │ │ s: Var (name=loan) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_6cf0e34e_uri, value=http://example.com/theme/library/Loan, anonymous) + │ ║ │ └── Extension (resultSizeActual=10.1K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=10.1K) + │ ║ │ ║ s: Var (name=loan) + │ ║ │ ║ p: Var (name=_const_945d14c4_uri, value=http://example.com/theme/library/dueDate, anonymous) + │ ║ │ ║ o: Var (name=due) + │ ║ │ ╚══ ExtensionElem (optDue) + │ ║ │ Var (name=due) + │ ║ └── Filter (new scope) (resultSizeActual=1.1K) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=name) + │ ║ ║ └── ValueConstant (value="member 1") + │ ║ ╚══ StatementPattern (resultSizeEstimate=45.3K, resultSizeActual=45.3K) + │ ║ s: Var (name=member) + │ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=loan) + └── ExtensionElem (count) + Count (Distinct) + Var (name=loan) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-4.txt new file mode 100644 index 00000000000..26bcc15732a --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-4.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=172.2K) + │ ║ │ s: Var (name=book) + │ ║ │ p: Var (name=_const_469a1e31_uri, value=http://example.com/theme/library/hasCopy, anonymous) + │ ║ │ o: Var (name=copy) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=0) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=0) [left] + │ ║ ║ ├── StatementPattern (costEstimate=64.4K, resultSizeEstimate=128.9K, resultSizeActual=128.8K) [left] + │ ║ ║ │ s: Var (name=book) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_6cec5947_uri, value=http://example.com/theme/library/Book, anonymous) + │ ║ ║ └── Filter (resultSizeActual=0) [right] + │ ║ ║ ╠══ Or + │ ║ ║ ║ ├── Compare (=) + │ ║ ║ ║ │ Var (name=title) + │ ║ ║ ║ │ ValueConstant (value="Book 1") + │ ║ ║ ║ └── Compare (=) + │ ║ ║ ║ Var (name=title) + │ ║ ║ ║ ValueConstant (value="Book 2") + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=128.8K) + │ ║ ║ s: Var (name=book) + │ ║ ║ p: Var (name=_const_335cbfda_uri, value=http://example.com/theme/library/title, anonymous) + │ ║ ║ o: Var (name=title) + │ ║ ╚══ StatementPattern (resultSizeEstimate=2.00) [right] + │ ║ s: Var (name=book) + │ ║ p: Var (name=_const_e1624c50_uri, value=http://example.com/theme/library/writtenBy, anonymous) + │ ║ o: Var (name=author) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=book) + └── ExtensionElem (count) + Count (Distinct) + Var (name=book) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-5.txt new file mode 100644 index 00000000000..87b458bd996 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-5.txt @@ -0,0 +1,39 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=217) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ╠══ Compare (<) + │ ║ │ ║ Var (name=due) + │ ║ │ ║ Var (name=threshold) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=347, resultSizeActual=217) + │ ║ │ s: Var (name=loan) + │ ║ │ p: Var (name=_const_945d14c4_uri, value=http://example.com/theme/library/dueDate, anonymous) + │ ║ │ o: Var (name=due) + │ ║ └── Join (JoinIterator) (resultSizeActual=217) + │ ║ ╠══ BindingSetAssignment ([[threshold="2024-01-01"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=217) [right] + │ ║ ├── Filter (resultSizeActual=217) [left] + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=loanDate) + │ ║ │ ║ ValueConstant (value="2024-01-01"^^) + │ ║ │ ║ ValueConstant (value="2024-01-02"^^) + │ ║ │ ╚══ StatementPattern (costEstimate=29.6K, resultSizeEstimate=10.1K, resultSizeActual=10.1K) + │ ║ │ s: Var (name=loan) + │ ║ │ p: Var (name=_const_f4588bfc_uri, value=http://example.com/theme/library/loanDate, anonymous) + │ ║ │ o: Var (name=loanDate) + │ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.81, resultSizeActual=217) [right] + │ ║ s: Var (name=loan) + │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ o: Var (name=_const_6cf0e34e_uri, value=http://example.com/theme/library/Loan, anonymous) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=loan) + └── ExtensionElem (count) + Count (Distinct) + Var (name=loan) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-6.txt new file mode 100644 index 00000000000..8702d64cde4 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-6.txt @@ -0,0 +1,53 @@ +Timed out while retrieving explanation! Explanation may be incomplete! +You can change the timeout by setting .setMaxExecutionTime(...) on your query. + +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "member" +║ ProjectionElem "loanCount" +╚══ Extension (resultSizeActual=0) + ├── Extension (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (member) (resultSizeActual=0) + │ ║ Filter (resultSizeActual=14.4M) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optCopy) + │ ║ │ Var (name=member) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=14.4M) + │ ║ ╠══ Union (resultSizeActual=11.5K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=10.1K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=5.1K, resultSizeEstimate=10.1K, resultSizeActual=10.1K) [left] + │ ║ ║ │ ║ s: Var (name=loan) + │ ║ ║ │ ║ p: Var (name=_const_b9a39489_uri, value=http://example.com/theme/library/borrowedBy, anonymous) + │ ║ ║ │ ║ o: Var (name=member) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.82, resultSizeActual=10.1K) [right] + │ ║ ║ │ s: Var (name=loan) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_6cf0e34e_uri, value=http://example.com/theme/library/Loan, anonymous) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=128.3K, resultSizeActual=1.4K) + │ ║ ║ s: Var (name=member) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_f5728978_uri, value=http://example.com/theme/library/Member, anonymous) + │ ║ ╚══ Extension (resultSizeActual=14.4M) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=14.4M) + │ ║ │ s: Var (name=loan) + │ ║ │ p: Var (name=_const_78c99d62_uri, value=http://example.com/theme/library/loanedCopy, anonymous) + │ ║ │ o: Var (name=copy) + │ ║ └── ExtensionElem (optCopy) + │ ║ Var (name=copy) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=loan) + │ ║ GroupElem (loanCount) + │ ║ Count (Distinct) + │ ║ Var (name=loan) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=loan) + └── ExtensionElem (loanCount) + Count (Distinct) + Var (name=loan) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-7.txt new file mode 100644 index 00000000000..81f22f85eba --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-7.txt @@ -0,0 +1,53 @@ +Timed out while retrieving explanation! Explanation may be incomplete! +You can change the timeout by setting .setMaxExecutionTime(...) on your query. + +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=0) + ├── Group () (resultSizeActual=0) + │ ╠══ Difference (resultSizeActual=0) + │ ║ ├── Join (JoinIterator) (resultSizeActual=24.1K) + │ ║ │ ╠══ Filter (resultSizeActual=1) [left] + │ ║ │ ║ ├── Or + │ ║ │ ║ │ ╠══ Compare (=) + │ ║ │ ║ │ ║ Var (name=branchName) + │ ║ │ ║ │ ║ ValueConstant (value="Branch 0") + │ ║ │ ║ │ ╚══ Compare (=) + │ ║ │ ║ │ Var (name=branchName) + │ ║ │ ║ │ ValueConstant (value="Branch 1") + │ ║ │ ║ └── StatementPattern (costEstimate=22.7K, resultSizeEstimate=45.3K, resultSizeActual=40.2K) + │ ║ │ ║ s: Var (name=branch) + │ ║ │ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ │ ║ o: Var (name=branchName) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=24.1K) [right] + │ ║ │ ├── Filter (resultSizeActual=24.1K) [left] + │ ║ │ │ ╠══ Exists + │ ║ │ │ ║ StatementPattern (resultSizeEstimate=127.8K, resultSizeActual=0) + │ ║ │ │ ║ s: Var (name=copy) + │ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ o: Var (name=_const_6ceccdd3_uri, value=http://example.com/theme/library/Copy, anonymous) + │ ║ │ │ ╚══ StatementPattern (costEstimate=116, resultSizeEstimate=53.8K, resultSizeActual=24.1K) + │ ║ │ │ s: Var (name=copy) + │ ║ │ │ p: Var (name=_const_ecfc63a7_uri, value=http://example.com/theme/library/locatedAt, anonymous) + │ ║ │ │ o: Var (name=branch) + │ ║ │ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.82, resultSizeActual=24.1K) [right] + │ ║ │ s: Var (name=copy) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_6ceccdd3_uri, value=http://example.com/theme/library/Copy, anonymous) + │ ║ └── Filter (new scope) (resultSizeActual=77.1K) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── Str + │ ║ ║ │ Var (name=branch) + │ ║ ║ └── ValueConstant (value="branch/0") + │ ║ ╚══ StatementPattern (resultSizeEstimate=258.7K, resultSizeActual=386.3K) + │ ║ s: Var (name=copy) + │ ║ p: Var (name=_const_ecfc63a7_uri, value=http://example.com/theme/library/locatedAt, anonymous) + │ ║ o: Var (name=branch) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=copy) + └── ExtensionElem (count) + Count (Distinct) + Var (name=copy) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-8.txt new file mode 100644 index 00000000000..2e080b25f01 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-8.txt @@ -0,0 +1,69 @@ +Projection (resultSizeActual=10) +╠══ ProjectionElemList +║ ProjectionElem "author" +║ ProjectionElem "loanCount" +╚══ Extension (resultSizeActual=10) + ├── Extension (resultSizeActual=10) + │ ╠══ Filter (resultSizeActual=10) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (author) (resultSizeActual=10) + │ ║ Filter (resultSizeActual=10) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="Member 1") + │ ║ │ ValueConstant (value="Member 2") + │ ║ │ ValueConstant (value="Member 3") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=20.2K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=20.2K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=3.4K, resultSizeEstimate=10.1K, resultSizeActual=10.1K) [left] + │ ║ ║ │ s: Var (name=loan) + │ ║ ║ │ p: Var (name=_const_b9a39489_uri, value=http://example.com/theme/library/borrowedBy, anonymous) + │ ║ ║ │ o: Var (name=member) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=20.2K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.82, resultSizeActual=10.1K) [left] + │ ║ ║ ║ s: Var (name=loan) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_6cf0e34e_uri, value=http://example.com/theme/library/Loan, anonymous) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=20.2K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=0.82, resultSizeEstimate=1.00, resultSizeActual=10.1K) [left] + │ ║ ║ │ s: Var (name=loan) + │ ║ ║ │ p: Var (name=_const_78c99d62_uri, value=http://example.com/theme/library/loanedCopy, anonymous) + │ ║ ║ │ o: Var (name=copy) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=20.2K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=0.82, resultSizeEstimate=1.00, resultSizeActual=10.1K) [left] + │ ║ ║ ║ s: Var (name=book) + │ ║ ║ ║ p: Var (name=_const_469a1e31_uri, value=http://example.com/theme/library/hasCopy, anonymous) + │ ║ ║ ║ o: Var (name=copy) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=20.2K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.82, resultSizeActual=10.1K) [left] + │ ║ ║ │ s: Var (name=book) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_6cec5947_uri, value=http://example.com/theme/library/Book, anonymous) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=20.2K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=10.1K) [left] + │ ║ ║ ║ s: Var (name=copy) + │ ║ ║ ║ p: Var (name=_const_ecfc63a7_uri, value=http://example.com/theme/library/locatedAt, anonymous) + │ ║ ║ ║ o: Var (name=branch) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.64, resultSizeEstimate=2.00, resultSizeActual=20.2K) [right] + │ ║ ║ s: Var (name=book) + │ ║ ║ p: Var (name=_const_e1624c50_uri, value=http://example.com/theme/library/writtenBy, anonymous) + │ ║ ║ o: Var (name=author) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=20.2K) [right] + │ ║ s: Var (name=member) + │ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ o: Var (name=optName) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=loan) + │ ║ GroupElem (loanCount) + │ ║ Count (Distinct) + │ ║ Var (name=loan) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=loan) + └── ExtensionElem (loanCount) + Count (Distinct) + Var (name=loan) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-9.txt new file mode 100644 index 00000000000..eef76912859 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/LIBRARY/query-9.txt @@ -0,0 +1,76 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=3) + │ ║ ├── And + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optTitle) + │ ║ │ ║ ValueConstant (value="") + │ ║ │ ╚══ Not + │ ║ │ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ├── Compare (<) + │ ║ │ │ Var (name=due) + │ ║ │ │ ValueConstant (value="2024-01-10"^^) + │ ║ │ └── StatementPattern (resultSizeEstimate=347, resultSizeActual=3) + │ ║ │ s: Var (name=loan) + │ ║ │ p: Var (name=_const_945d14c4_uri, value=http://example.com/theme/library/dueDate, anonymous) + │ ║ │ o: Var (name=due) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=3) + │ ║ ╠══ Filter (resultSizeActual=3) [left] + │ ║ ║ ├── Or + │ ║ ║ │ ╠══ Compare (=) + │ ║ ║ │ ║ Var (name=authorName) + │ ║ ║ │ ║ Var (name=target) + │ ║ ║ │ ╚══ Compare (=) + │ ║ ║ │ Var (name=authorName) + │ ║ ║ │ ValueConstant (value="Author 3") + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=40.4K) + │ ║ ║ ╠══ BindingSetAssignment ([[target="Author 1"], [target="Author 2"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=15.2K, resultSizeEstimate=10.1K, resultSizeActual=20.3K) [left] + │ ║ ║ │ s: Var (name=loan) + │ ║ ║ │ p: Var (name=_const_b9a39489_uri, value=http://example.com/theme/library/borrowedBy, anonymous) + │ ║ ║ │ o: Var (name=member) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.85, resultSizeActual=20.3K) [left] + │ ║ ║ ║ s: Var (name=member) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_f5728978_uri, value=http://example.com/theme/library/Member, anonymous) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.85, resultSizeActual=20.3K) [left] + │ ║ ║ │ s: Var (name=loan) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_6cf0e34e_uri, value=http://example.com/theme/library/Loan, anonymous) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=20.3K) [left] + │ ║ ║ ║ s: Var (name=loan) + │ ║ ║ ║ p: Var (name=_const_78c99d62_uri, value=http://example.com/theme/library/loanedCopy, anonymous) + │ ║ ║ ║ o: Var (name=copy) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=20.3K) [left] + │ ║ ║ │ s: Var (name=book) + │ ║ ║ │ p: Var (name=_const_469a1e31_uri, value=http://example.com/theme/library/hasCopy, anonymous) + │ ║ ║ │ o: Var (name=copy) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=40.4K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.32, resultSizeEstimate=1.99, resultSizeActual=40.4K) [left] + │ ║ ║ ║ s: Var (name=book) + │ ║ ║ ║ p: Var (name=_const_e1624c50_uri, value=http://example.com/theme/library/writtenBy, anonymous) + │ ║ ║ ║ o: Var (name=author) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=40.4K) [right] + │ ║ ║ s: Var (name=author) + │ ║ ║ p: Var (name=_const_6d0024c9_uri, value=http://example.com/theme/library/name, anonymous) + │ ║ ║ o: Var (name=authorName) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=3) [right] + │ ║ s: Var (name=book) + │ ║ p: Var (name=_const_335cbfda_uri, value=http://example.com/theme/library/title, anonymous) + │ ║ o: Var (name=optTitle) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=member) + └── ExtensionElem (count) + Count (Distinct) + Var (name=member) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-0.txt new file mode 100644 index 00000000000..05d852dfc75 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-0.txt @@ -0,0 +1,38 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=29.1K) + │ ║ ├── Filter (resultSizeActual=14.5K) [left] + │ ║ │ ╠══ Compare (>=) + │ ║ │ ║ Var (name=optDate) + │ ║ │ ║ ValueConstant (value="2024-06-01"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=24.9K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=8.3K, resultSizeActual=8.3K) [left] + │ ║ │ │ s: Var (name=patient) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ │ └── Extension (resultSizeActual=24.9K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=24.9K) + │ ║ │ ║ ├── StatementPattern (costEstimate=1.48, resultSizeEstimate=3.10, resultSizeActual=24.9K) [left] + │ ║ │ ║ │ s: Var (name=patient) + │ ║ │ ║ │ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ │ ║ │ o: Var (name=enc) + │ ║ │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=24.9K) [right] + │ ║ │ ║ s: Var (name=enc) + │ ║ │ ║ p: Var (name=_const_2816f2d7_uri, value=http://example.com/theme/medical/recordedOn, anonymous) + │ ║ │ ║ o: Var (name=date) + │ ║ │ ╚══ ExtensionElem (optDate) + │ ║ │ Var (name=date) + │ ║ └── StatementPattern (resultSizeEstimate=2.01, resultSizeActual=29.1K) [right] + │ ║ s: Var (name=patient) + │ ║ p: Var (name=_const_fe9f43e1_uri, value=http://example.com/theme/medical/hasMedication, anonymous) + │ ║ o: Var (name=med) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=patient) + └── ExtensionElem (count) + Count (Distinct) + Var (name=patient) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-1.txt new file mode 100644 index 00000000000..bd8d0f4df6a --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-1.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=58.0K) + │ ║ ├── Filter (resultSizeActual=58.0K) [left] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=code) + │ ║ │ ║ │ Var (name=target) + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=code) + │ ║ │ ║ ValueConstant (value="DX-202") + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=133.0K) + │ ║ │ ├── BindingSetAssignment ([[target="DX-200"], [target="DX-201"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ └── Union (resultSizeActual=133.0K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=99.6K) + │ ║ │ ║ ├── StatementPattern (costEstimate=64.6K, resultSizeEstimate=21.5K, resultSizeActual=99.6K) [left] + │ ║ │ ║ │ s: Var (name=entity) + │ ║ │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ │ o: Var (name=_const_d05fbbd3_uri, value=http://example.com/theme/medical/Condition, anonymous) + │ ║ │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=99.6K) [right] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ │ ║ o: Var (name=code) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=33.3K) + │ ║ │ ├── StatementPattern (costEstimate=726.4M, resultSizeEstimate=21.5K, resultSizeActual=33.3K) [left] + │ ║ │ │ s: Var (name=entity) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_ea395317_uri, value=http://example.com/theme/medical/Medication, anonymous) + │ ║ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=33.3K) [right] + │ ║ │ s: Var (name=entity) + │ ║ │ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ │ o: Var (name=code) + │ ║ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=58.0K) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ o: Var (name=alt) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-10.txt new file mode 100644 index 00000000000..bc4989fd5ff --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-10.txt @@ -0,0 +1,61 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=66.3K) + │ ║ ├── And + │ ║ │ ╠══ Not + │ ║ │ ║ Exists + │ ║ │ ║ Join (JoinIterator) (resultSizeActual=0) + │ ║ │ ║ ├── StatementPattern (costEstimate=217425.3M, resultSizeEstimate=5.8K, resultSizeActual=138.8K) [left] + │ ║ │ ║ │ s: Var (name=patient) + │ ║ │ ║ │ p: Var (name=_const_fe9f43e1_uri, value=http://example.com/theme/medical/hasMedication, anonymous) + │ ║ │ ║ │ o: Var (name=m2) + │ ║ │ ║ └── Filter (resultSizeActual=0) [right] + │ ║ │ ║ ╠══ Compare (=) + │ ║ │ ║ ║ Var (name=c) + │ ║ │ ║ ║ ValueConstant (value="MED-1005") + │ ║ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=138.8K) + │ ║ │ ║ s: Var (name=m2) + │ ║ │ ║ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ │ ║ o: Var (name=c) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=66.3K) + │ ║ ╠══ Union (resultSizeActual=66.3K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=16.6K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=9.0K, resultSizeEstimate=18.1K, resultSizeActual=8.3K) [left] + │ ║ ║ │ ║ s: Var (name=patient) + │ ║ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ ║ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=2.65, resultSizeEstimate=2.01, resultSizeActual=16.6K) [right] + │ ║ ║ │ s: Var (name=patient) + │ ║ ║ │ p: Var (name=_const_fe9f43e1_uri, value=http://example.com/theme/medical/hasMedication, anonymous) + │ ║ ║ │ o: Var (name=med) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=49.6K) + │ ║ ║ ╠══ StatementPattern (costEstimate=75.1M, resultSizeEstimate=24.9K, resultSizeActual=24.9K) [left] + │ ║ ║ ║ s: Var (name=patient) + │ ║ ║ ║ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ ║ ║ o: Var (name=enc) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=49.6K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.99, resultSizeActual=24.9K) [left] + │ ║ ║ │ s: Var (name=patient) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ ║ └── StatementPattern (costEstimate=2.64, resultSizeEstimate=1.99, resultSizeActual=49.6K) [right] + │ ║ ║ s: Var (name=enc) + │ ║ ║ p: Var (name=_const_6f00815a_uri, value=http://example.com/theme/medical/hasObservation, anonymous) + │ ║ ║ o: Var (name=obs) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=66.3K) [right] + │ ║ s: Var (name=patient) + │ ║ p: Var (name=_const_99364b3_uri, value=http://example.com/theme/medical/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=patient) + └── ExtensionElem (count) + Count (Distinct) + Var (name=patient) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-2.txt new file mode 100644 index 00000000000..4254f0e6636 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-2.txt @@ -0,0 +1,48 @@ +Projection (resultSizeActual=135) +╠══ ProjectionElemList +║ ProjectionElem "practitioner" +║ ProjectionElem "encCount" +╚══ Extension (resultSizeActual=135) + ├── Extension (resultSizeActual=135) + │ ╠══ Filter (resultSizeActual=135) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (practitioner) (resultSizeActual=135) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=266) + │ ║ ├── Join (JoinIterator) (resultSizeActual=135) [left] + │ ║ │ ╠══ StatementPattern (costEstimate=8.2K, resultSizeEstimate=24.0K, resultSizeActual=24.9K) [left] + │ ║ │ ║ s: Var (name=enc) + │ ║ │ ║ p: Var (name=_const_9016af8b_uri, value=http://example.com/theme/medical/handledBy, anonymous) + │ ║ │ ║ o: Var (name=practitioner) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=135) [right] + │ ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.97, resultSizeActual=24.9K) [left] + │ ║ │ │ s: Var (name=enc) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_5e8eb7eb_uri, value=http://example.com/theme/medical/Encounter, anonymous) + │ ║ │ └── Filter (resultSizeActual=135) [right] + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=date) + │ ║ │ ║ ValueConstant (value="2024-01-01"^^) + │ ║ │ ║ ValueConstant (value="2024-02-01"^^) + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=24.9K) + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_2816f2d7_uri, value=http://example.com/theme/medical/recordedOn, anonymous) + │ ║ │ o: Var (name=date) + │ ║ └── StatementPattern (resultSizeEstimate=1.97, resultSizeActual=266) [right] + │ ║ s: Var (name=enc) + │ ║ p: Var (name=_const_7e7389c9_uri, value=http://example.com/theme/medical/hasCondition, anonymous) + │ ║ o: Var (name=cond) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=enc) + │ ║ GroupElem (encCount) + │ ║ Count (Distinct) + │ ║ Var (name=enc) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=enc) + └── ExtensionElem (encCount) + Count (Distinct) + Var (name=enc) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-3.txt new file mode 100644 index 00000000000..56a0f0ff2cf --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-3.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=38.7K) + │ ║ ├── Filter (resultSizeActual=38.7K) + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optValue) + │ ║ │ ║ ValueConstant (value="60"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=49.6K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=19.8K, resultSizeActual=8.3K) [left] + │ ║ │ │ s: Var (name=patient) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ │ └── Extension (resultSizeActual=49.6K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=49.6K) + │ ║ │ ║ ├── StatementPattern (costEstimate=1.41, resultSizeEstimate=3.00, resultSizeActual=24.9K) [left] + │ ║ │ ║ │ s: Var (name=patient) + │ ║ │ ║ │ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ │ ║ │ o: Var (name=_anon_path_1, anonymous) + │ ║ │ ║ └── Join (JoinIterator) (resultSizeActual=49.6K) [right] + │ ║ │ ║ ╠══ StatementPattern (costEstimate=1.32, resultSizeEstimate=1.99, resultSizeActual=49.6K) [left] + │ ║ │ ║ ║ s: Var (name=_anon_path_1, anonymous) + │ ║ │ ║ ║ p: Var (name=_const_6f00815a_uri, value=http://example.com/theme/medical/hasObservation, anonymous) + │ ║ │ ║ ║ o: Var (name=obs) + │ ║ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=49.6K) [right] + │ ║ │ ║ s: Var (name=obs) + │ ║ │ ║ p: Var (name=_const_2949ec49_uri, value=http://example.com/theme/medical/value, anonymous) + │ ║ │ ║ o: Var (name=value) + │ ║ │ ╚══ ExtensionElem (optValue) + │ ║ │ Var (name=value) + │ ║ └── Filter (new scope) (resultSizeActual=0) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=name) + │ ║ ║ └── ValueConstant (value="test") + │ ║ ╚══ StatementPattern (resultSizeEstimate=21.4K, resultSizeActual=21.4K) + │ ║ s: Var (name=patient) + │ ║ p: Var (name=_const_99364b3_uri, value=http://example.com/theme/medical/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=patient) + └── ExtensionElem (count) + Count (Distinct) + Var (name=patient) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-4.txt new file mode 100644 index 00000000000..639462cd717 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-4.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=41.6K) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=15.1K, resultSizeActual=0) + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_6f00815a_uri, value=http://example.com/theme/medical/hasObservation, anonymous) + │ ║ │ o: Var (name=obs) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=41.6K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=41.6K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=9.7K, resultSizeEstimate=19.3K, resultSizeActual=24.9K) [left] + │ ║ ║ │ s: Var (name=enc) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_5e8eb7eb_uri, value=http://example.com/theme/medical/Encounter, anonymous) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=41.6K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.32, resultSizeEstimate=1.97, resultSizeActual=49.8K) [left] + │ ║ ║ ║ s: Var (name=enc) + │ ║ ║ ║ p: Var (name=_const_7e7389c9_uri, value=http://example.com/theme/medical/hasCondition, anonymous) + │ ║ ║ ║ o: Var (name=cond) + │ ║ ║ ╚══ Filter (resultSizeActual=41.6K) [right] + │ ║ ║ ├── Or + │ ║ ║ │ ╠══ Compare (=) + │ ║ ║ │ ║ Var (name=code) + │ ║ ║ │ ║ ValueConstant (value="DX-200") + │ ║ ║ │ ╚══ Compare (=) + │ ║ ║ │ Var (name=code) + │ ║ ║ │ ValueConstant (value="DX-201") + │ ║ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=49.8K) + │ ║ ║ s: Var (name=cond) + │ ║ ║ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ ║ o: Var (name=code) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=41.6K) [right] + │ ║ s: Var (name=enc) + │ ║ p: Var (name=_const_9016af8b_uri, value=http://example.com/theme/medical/handledBy, anonymous) + │ ║ o: Var (name=practitioner) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=enc) + └── ExtensionElem (count) + Count (Distinct) + Var (name=enc) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-5.txt new file mode 100644 index 00000000000..6ac6b18d13b --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-5.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ StatementPattern (resultSizeEstimate=32.2K, resultSizeActual=0) + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_7e7389c9_uri, value=http://example.com/theme/medical/hasCondition, anonymous) + │ ║ │ o: Var (name=cond) + │ ║ └── Join (JoinIterator) (resultSizeActual=3.0K) + │ ║ ╠══ BindingSetAssignment ([[limit="55"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=3.0K) [right] + │ ║ ├── StatementPattern (costEstimate=49.5K, resultSizeEstimate=24.8K, resultSizeActual=24.9K) [left] + │ ║ │ s: Var (name=patient) + │ ║ │ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ │ o: Var (name=enc) + │ ║ └── Join (JoinIterator) (resultSizeActual=3.0K) [right] + │ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.99, resultSizeActual=24.9K) [left] + │ ║ ║ s: Var (name=patient) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=3.0K) [right] + │ ║ ├── StatementPattern (costEstimate=1.32, resultSizeEstimate=1.99, resultSizeActual=49.6K) [left] + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_6f00815a_uri, value=http://example.com/theme/medical/hasObservation, anonymous) + │ ║ │ o: Var (name=obs) + │ ║ └── Filter (resultSizeActual=3.0K) [right] + │ ║ ╠══ ListMemberOperator + │ ║ ║ Var (name=value) + │ ║ ║ ValueConstant (value="50"^^) + │ ║ ║ ValueConstant (value="60"^^) + │ ║ ║ ValueConstant (value="70"^^) + │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=49.6K) + │ ║ s: Var (name=obs) + │ ║ p: Var (name=_const_2949ec49_uri, value=http://example.com/theme/medical/value, anonymous) + │ ║ o: Var (name=value) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=patient) + └── ExtensionElem (count) + Count (Distinct) + Var (name=patient) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-6.txt new file mode 100644 index 00000000000..042f0b46cac --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-6.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=8.3K) +╠══ ProjectionElemList +║ ProjectionElem "patient" +║ ProjectionElem "medCount" +╚══ Extension (resultSizeActual=8.3K) + ├── Extension (resultSizeActual=8.3K) + │ ╠══ Filter (resultSizeActual=8.3K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (patient) (resultSizeActual=8.3K) + │ ║ Filter (resultSizeActual=66.8K) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optMed) + │ ║ │ Var (name=patient) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=66.8K) + │ ║ ╠══ Union (resultSizeActual=33.3K) [left] + │ ║ ║ ├── StatementPattern (new scope) (resultSizeEstimate=18.0K, resultSizeActual=8.3K) + │ ║ ║ │ s: Var (name=patient) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=24.9K, resultSizeActual=24.9K) + │ ║ ║ s: Var (name=patient) + │ ║ ║ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ ║ o: Var (name=enc) + │ ║ ╚══ Extension (resultSizeActual=66.8K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=2.01, resultSizeActual=66.8K) + │ ║ │ s: Var (name=patient) + │ ║ │ p: Var (name=_const_fe9f43e1_uri, value=http://example.com/theme/medical/hasMedication, anonymous) + │ ║ │ o: Var (name=med) + │ ║ └── ExtensionElem (optMed) + │ ║ Var (name=med) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=med) + │ ║ GroupElem (medCount) + │ ║ Count (Distinct) + │ ║ Var (name=med) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=med) + └── ExtensionElem (medCount) + Count (Distinct) + Var (name=med) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-7.txt new file mode 100644 index 00000000000..b7fe66bf1ff --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-7.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=0) + │ ║ ├── Filter (resultSizeActual=13.8K) + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=5.8K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=patient) + │ ║ │ ║ p: Var (name=_const_fe9f43e1_uri, value=http://example.com/theme/medical/hasMedication, anonymous) + │ ║ │ ║ o: Var (name=med) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=13.8K) + │ ║ │ ├── StatementPattern (costEstimate=8.9K, resultSizeEstimate=17.8K, resultSizeActual=16.6K) [left] + │ ║ │ │ s: Var (name=med) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_ea395317_uri, value=http://example.com/theme/medical/Medication, anonymous) + │ ║ │ └── Filter (resultSizeActual=13.8K) [right] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=code) + │ ║ │ ║ │ ValueConstant (value="MED-1000") + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=code) + │ ║ │ ║ ValueConstant (value="MED-1001") + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=16.6K) + │ ║ │ s: Var (name=med) + │ ║ │ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ │ o: Var (name=code) + │ ║ └── Filter (new scope) (resultSizeActual=16.6K) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=dose) + │ ║ ║ └── ValueConstant (value="x") + │ ║ ╚══ StatementPattern (resultSizeEstimate=16.7K, resultSizeActual=16.6K) + │ ║ s: Var (name=med) + │ ║ p: Var (name=_const_e2048edf_uri, value=http://example.com/theme/medical/dosage, anonymous) + │ ║ o: Var (name=dose) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=med) + └── ExtensionElem (count) + Count (Distinct) + Var (name=med) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-8.txt new file mode 100644 index 00000000000..29929557b9e --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-8.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=8.3K) +╠══ ProjectionElemList +║ ProjectionElem "patient" +║ ProjectionElem "encCount" +╚══ Extension (resultSizeActual=8.3K) + ├── Extension (resultSizeActual=8.3K) + │ ╠══ Filter (resultSizeActual=8.3K) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="2"^^) + │ ║ └── Group (patient) (resultSizeActual=8.3K) + │ ║ Filter (resultSizeActual=24.9K) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=32.2K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=enc) + │ ║ │ ║ p: Var (name=_const_7e7389c9_uri, value=http://example.com/theme/medical/hasCondition, anonymous) + │ ║ │ ║ o: Var (name=cond) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optPractitioner) + │ ║ │ Var (name=patient) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=24.9K) + │ ║ ╠══ StatementPattern (resultSizeEstimate=17.6K, resultSizeActual=8.3K) [left] + │ ║ ║ s: Var (name=patient) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_24be87bd_uri, value=http://example.com/theme/medical/Patient, anonymous) + │ ║ ╚══ Extension (resultSizeActual=24.9K) [right] + │ ║ ├── Join (JoinIterator) (resultSizeActual=24.9K) + │ ║ │ ╠══ StatementPattern (costEstimate=1.41, resultSizeEstimate=3.00, resultSizeActual=24.9K) [left] + │ ║ │ ║ s: Var (name=patient) + │ ║ │ ║ p: Var (name=_const_ca285e1_uri, value=http://example.com/theme/medical/hasEncounter, anonymous) + │ ║ │ ║ o: Var (name=enc) + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=24.9K) [right] + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_9016af8b_uri, value=http://example.com/theme/medical/handledBy, anonymous) + │ ║ │ o: Var (name=practitioner) + │ ║ └── ExtensionElem (optPractitioner) + │ ║ Var (name=practitioner) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=enc) + │ ║ GroupElem (encCount) + │ ║ Count (Distinct) + │ ║ Var (name=enc) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=enc) + └── ExtensionElem (encCount) + Count (Distinct) + Var (name=enc) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-9.txt new file mode 100644 index 00000000000..b9032e095e2 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/MEDICAL_RECORDS/query-9.txt @@ -0,0 +1,53 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=65.3K) + │ ║ ├── LeftJoin (LeftJoinIterator) (resultSizeActual=99.6K) + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=99.6K) [left] + │ ║ │ ║ ├── BindingSetAssignment ([[code="DX-200"], [code="DX-201"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ ║ └── Join (JoinIterator) (resultSizeActual=99.6K) [right] + │ ║ │ ║ ╠══ StatementPattern (costEstimate=52.7K, resultSizeEstimate=17.6K, resultSizeActual=49.9K) [left] + │ ║ │ ║ ║ s: Var (name=enc) + │ ║ │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ ║ o: Var (name=_const_5e8eb7eb_uri, value=http://example.com/theme/medical/Encounter, anonymous) + │ ║ │ ║ ╚══ Join (JoinIterator) (resultSizeActual=99.6K) [right] + │ ║ │ ║ ├── StatementPattern (costEstimate=1.32, resultSizeEstimate=2.00, resultSizeActual=99.6K) [left] + │ ║ │ ║ │ s: Var (name=enc) + │ ║ │ ║ │ p: Var (name=_const_7e7389c9_uri, value=http://example.com/theme/medical/hasCondition, anonymous) + │ ║ │ ║ │ o: Var (name=cond) + │ ║ │ ║ └── Filter (resultSizeActual=99.6K) [right] + │ ║ │ ║ ╠══ ListMemberOperator + │ ║ │ ║ ║ Var (name=condCode) + │ ║ │ ║ ║ ValueConstant (value="DX-200") + │ ║ │ ║ ║ ValueConstant (value="DX-201") + │ ║ │ ║ ║ ValueConstant (value="DX-202") + │ ║ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=99.6K) + │ ║ │ ║ s: Var (name=cond) + │ ║ │ ║ p: Var (name=_const_98e9815_uri, value=http://example.com/theme/medical/code, anonymous) + │ ║ │ ║ o: Var (name=condCode) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=99.6K) [right] + │ ║ │ s: Var (name=enc) + │ ║ │ p: Var (name=_const_9016af8b_uri, value=http://example.com/theme/medical/handledBy, anonymous) + │ ║ │ o: Var (name=practitioner) + │ ║ └── Join (HashJoinIteration) (resultSizeActual=9.8K) + │ ║ ╠══ StatementPattern (costEstimate=1320.9M, resultSizeEstimate=48.7K, resultSizeActual=49.6K) [left] + │ ║ ║ s: Var (name=enc) + │ ║ ║ p: Var (name=_const_6f00815a_uri, value=http://example.com/theme/medical/hasObservation, anonymous) + │ ║ ║ o: Var (name=obs) + │ ║ ╚══ Filter (new scope) (resultSizeActual=9.8K) [right] + │ ║ ├── Compare (<) + │ ║ │ Var (name=value) + │ ║ │ ValueConstant (value="60"^^) + │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=49.6K) + │ ║ s: Var (name=obs) + │ ║ p: Var (name=_const_2949ec49_uri, value=http://example.com/theme/medical/value, anonymous) + │ ║ o: Var (name=value) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=enc) + └── ExtensionElem (count) + Count (Distinct) + Var (name=enc) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-0.txt new file mode 100644 index 00000000000..1a33e3f9c23 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-0.txt @@ -0,0 +1,68 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=18) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optMarker) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/biomarker/999) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=18) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=18) [left] + │ ║ ║ ├── BindingSetAssignment ([[disease=http://example.com/theme/pharma/disease/0], [disease=http://example.com/theme/pharma/disease/1]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=4.00, resultSizeActual=8) [left] + │ ║ ║ ║ s: Var (name=trial) + │ ║ ║ ║ p: Var (name=_const_5a7b59fd_uri, value=http://example.com/theme/pharma/studiesDisease, anonymous) + │ ║ ║ ║ o: Var (name=disease) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=8) [left] + │ ║ ║ │ s: Var (name=trial) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_4795bbfb_uri, value=http://example.com/theme/pharma/ClinicalTrial, anonymous) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=0.96, resultSizeEstimate=3.25, resultSizeActual=26) [left] + │ ║ ║ ║ s: Var (name=trial) + │ ║ ║ ║ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ ║ ║ o: Var (name=arm) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ├── StatementPattern (costEstimate=0.82, resultSizeEstimate=1.00, resultSizeActual=26) [left] + │ ║ ║ │ s: Var (name=arm) + │ ║ ║ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + │ ║ ║ │ o: Var (name=result) + │ ║ ║ └── Filter (resultSizeActual=18) [right] + │ ║ ║ ╠══ Or + │ ║ ║ ║ ├── Compare (<) + │ ║ ║ ║ │ Var (name=p) + │ ║ ║ ║ │ ValueConstant (value="0.05"^^) + │ ║ ║ ║ └── Compare (>) + │ ║ ║ ║ Var (name=effect) + │ ║ ║ ║ ValueConstant (value="0.7"^^) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=26) + │ ║ ║ ├── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=26) [left] + │ ║ ║ │ s: Var (name=result) + │ ║ ║ │ p: Var (name=_const_6999fbda_uri, value=http://example.com/theme/pharma/effectSize, anonymous) + │ ║ ║ │ o: Var (name=effect) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=26) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=26) [left] + │ ║ ║ ║ s: Var (name=result) + │ ║ ║ ║ p: Var (name=_const_80c71989_uri, value=http://example.com/theme/pharma/pValue, anonymous) + │ ║ ║ ║ o: Var (name=p) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=26) [right] + │ ║ ║ s: Var (name=arm) + │ ║ ║ p: Var (name=_const_aefd3274_uri, value=http://example.com/theme/pharma/armDrug, anonymous) + │ ║ ║ o: Var (name=drug) + │ ║ ╚══ Extension (resultSizeActual=18) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=18) + │ ║ │ s: Var (name=result) + │ ║ │ p: Var (name=_const_80a6979a_uri, value=http://example.com/theme/pharma/biomarker, anonymous) + │ ║ │ o: Var (name=marker) + │ ║ └── ExtensionElem (optMarker) + │ ║ Var (name=marker) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (count) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-1.txt new file mode 100644 index 00000000000..c3e4baf202f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-1.txt @@ -0,0 +1,60 @@ +Projection (resultSizeActual=80) +╠══ ProjectionElemList +║ ProjectionElem "combo" +║ ProjectionElem "drugCount" +╚══ Extension (resultSizeActual=80) + ├── Extension (resultSizeActual=80) + │ ╠══ Filter (resultSizeActual=80) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="2"^^) + │ ║ └── Group (combo) (resultSizeActual=134) + │ ║ Filter (resultSizeActual=388) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optSeverity) + │ ║ │ ValueConstant (value="Mild") + │ ║ │ ValueConstant (value="Moderate") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=593) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=285) [left] + │ ║ ║ ├── Filter (resultSizeActual=141) [left] + │ ║ ║ │ ╠══ Compare (>) + │ ║ ║ │ ║ Var (name=score) + │ ║ ║ │ ║ ValueConstant (value="0.7"^^) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=160, resultSizeEstimate=460, resultSizeActual=477) + │ ║ ║ │ s: Var (name=combo) + │ ║ ║ │ p: Var (name=_const_2c1ec653_uri, value=http://example.com/theme/pharma/synergyScore, anonymous) + │ ║ ║ │ o: Var (name=score) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=285) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=1.00, resultSizeActual=141) [left] + │ ║ ║ ║ s: Var (name=combo) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_a4089907_uri, value=http://example.com/theme/pharma/Combination, anonymous) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.65, resultSizeEstimate=2.02, resultSizeActual=285) [right] + │ ║ ║ s: Var (name=combo) + │ ║ ║ p: Var (name=_const_94a74d5e_uri, value=http://example.com/theme/pharma/combinationOf, anonymous) + │ ║ ║ o: Var (name=drug) + │ ║ ╚══ Extension (resultSizeActual=593) [right] + │ ║ ├── Join (JoinIterator) (resultSizeActual=593) + │ ║ │ ╠══ StatementPattern (costEstimate=1.35, resultSizeEstimate=2.17, resultSizeActual=593) [left] + │ ║ │ ║ s: Var (name=drug) + │ ║ │ ║ p: Var (name=_const_72f8dc5a_uri, value=http://example.com/theme/pharma/hasSideEffect, anonymous) + │ ║ │ ║ o: Var (name=sideEffect) + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=593) [right] + │ ║ │ s: Var (name=sideEffect) + │ ║ │ p: Var (name=_const_dff9bba5_uri, value=http://example.com/theme/pharma/severity, anonymous) + │ ║ │ o: Var (name=sev) + │ ║ └── ExtensionElem (optSeverity) + │ ║ Var (name=sev) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ║ GroupElem (drugCount) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (drugCount) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-10.txt new file mode 100644 index 00000000000..4b69126bd51 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-10.txt @@ -0,0 +1,68 @@ +Projection (resultSizeActual=51) +╠══ ProjectionElemList +║ ProjectionElem "pathway" +║ ProjectionElem "drugCount" +╚══ Extension (resultSizeActual=51) + ├── Extension (resultSizeActual=51) + │ ╠══ Filter (resultSizeActual=51) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="1"^^) + │ ║ └── Group (pathway) (resultSizeActual=105) + │ ║ Filter (resultSizeActual=206) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ Join (JoinIterator) (resultSizeActual=0) + │ ║ │ ║ ╠══ StatementPattern (costEstimate=319.6K, resultSizeEstimate=314, resultSizeActual=191.6K) [left] + │ ║ │ ║ ║ s: Var (name=result) + │ ║ │ ║ ║ p: Var (name=_const_80a6979a_uri, value=http://example.com/theme/pharma/biomarker, anonymous) + │ ║ │ ║ ║ o: Var (name=marker) + │ ║ │ ║ ╚══ Join (JoinIterator) (resultSizeActual=11.4K) [right] + │ ║ │ ║ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=191.6K) [left] + │ ║ │ ║ │ s: Var (name=arm) + │ ║ │ ║ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + │ ║ │ ║ │ o: Var (name=result) + │ ║ │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=11.4K) [right] + │ ║ │ ║ s: Var (name=trial) + │ ║ │ ║ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ │ ║ o: Var (name=arm) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optTrial) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/trial/0) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=22.6K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=19.8K) [left] + │ ║ ║ ├── BindingSetAssignment ([[marker=http://example.com/theme/pharma/biomarker/3], [marker=http://example.com/theme/pharma/biomarker/4]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=19.8K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=2.0K, resultSizeEstimate=664, resultSizeActual=1.3K) [left] + │ ║ ║ ║ s: Var (name=target) + │ ║ ║ ║ p: Var (name=_const_1a978c1d_uri, value=http://example.com/theme/pharma/inPathway, anonymous) + │ ║ ║ ║ o: Var (name=pathway) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=19.8K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=2.96, resultSizeEstimate=30, resultSizeActual=40.0K) [left] + │ ║ ║ │ s: Var (name=drug) + │ ║ ║ │ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ ║ ║ │ o: Var (name=target) + │ ║ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.79, resultSizeActual=19.8K) [right] + │ ║ ║ s: Var (name=drug) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_f6bbe068_uri, value=http://example.com/theme/pharma/Drug, anonymous) + │ ║ ╚══ Extension (resultSizeActual=11.3K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=0.57, resultSizeActual=11.3K) + │ ║ │ s: Var (name=drug) + │ ║ │ p: Var (name=_const_4389be5e_uri, value=http://example.com/theme/pharma/testedIn, anonymous) + │ ║ │ o: Var (name=trial) + │ ║ └── ExtensionElem (optTrial) + │ ║ Var (name=trial) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ║ GroupElem (drugCount) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (drugCount) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-2.txt new file mode 100644 index 00000000000..8814e87b5ae --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-2.txt @@ -0,0 +1,67 @@ +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "target" +║ ProjectionElem "drugCount" +╚══ Extension (resultSizeActual=0) + ├── Extension (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="2"^^) + │ ║ └── Group (target) (resultSizeActual=63) + │ ║ Filter (resultSizeActual=63) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ Join (JoinIterator) (resultSizeActual=0) + │ ║ │ ║ ╠══ StatementPattern (costEstimate=35.0K, resultSizeEstimate=313, resultSizeActual=8.6K) [left] + │ ║ │ ║ ║ s: Var (name=arm) + │ ║ │ ║ ║ p: Var (name=_const_aefd3274_uri, value=http://example.com/theme/pharma/armDrug, anonymous) + │ ║ │ ║ ║ o: Var (name=drug) + │ ║ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=8.6K) [right] + │ ║ │ ║ s: Var (name=trial) + │ ║ │ ║ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ │ ║ o: Var (name=arm) + │ ║ │ ╚══ ListMemberOperator + │ ║ │ Var (name=optDisease) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/disease/2) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/disease/3) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=19.7K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=9.9K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=220, resultSizeEstimate=655, resultSizeActual=666) [left] + │ ║ ║ │ s: Var (name=target) + │ ║ ║ │ p: Var (name=_const_1a978c1d_uri, value=http://example.com/theme/pharma/inPathway, anonymous) + │ ║ ║ │ o: Var (name=pathway) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=9.9K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.58, resultSizeActual=666) [left] + │ ║ ║ ║ s: Var (name=target) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_51a21059_uri, value=http://example.com/theme/pharma/Target, anonymous) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=9.9K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=2.96, resultSizeEstimate=30, resultSizeActual=20.0K) [left] + │ ║ ║ │ s: Var (name=drug) + │ ║ ║ │ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ ║ ║ │ o: Var (name=target) + │ ║ ║ └── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.60, resultSizeActual=9.9K) [right] + │ ║ ║ s: Var (name=drug) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_f6bbe068_uri, value=http://example.com/theme/pharma/Drug, anonymous) + │ ║ ╚══ Extension (resultSizeActual=19.7K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.99, resultSizeActual=19.7K) + │ ║ │ s: Var (name=drug) + │ ║ │ p: Var (name=_const_e46c34a6_uri, value=http://example.com/theme/pharma/indicatedFor, anonymous) + │ ║ │ o: Var (name=disease) + │ ║ └── ExtensionElem (optDisease) + │ ║ Var (name=disease) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ║ GroupElem (drugCount) + │ ║ Count (Distinct) + │ ║ Var (name=drug) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (drugCount) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-3.txt new file mode 100644 index 00000000000..69106b8c78e --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-3.txt @@ -0,0 +1,57 @@ +Projection (resultSizeActual=2.2K) +╠══ ProjectionElemList +║ ProjectionElem "drug" +║ ProjectionElem "disease" +╚══ Filter (resultSizeActual=2.2K) + ├── Compare (!=) + │ Var (name=optTarget) + │ ValueConstant (value=http://example.com/theme/pharma/target/0) + └── LeftJoin (LeftJoinIterator) (resultSizeActual=2.2K) + ╠══ Join (JoinIterator) (resultSizeActual=1.1K) [left] + ║ ├── StatementPattern (costEstimate=305, resultSizeEstimate=910, resultSizeActual=955) [left] + ║ │ s: Var (name=trial) + ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + ║ │ o: Var (name=_const_4795bbfb_uri, value=http://example.com/theme/pharma/ClinicalTrial, anonymous) + ║ └── Join (JoinIterator) (resultSizeActual=1.1K) [right] + ║ ╠══ StatementPattern (costEstimate=0.95, resultSizeEstimate=3.05, resultSizeActual=2.8K) [left] + ║ ║ s: Var (name=trial) + ║ ║ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + ║ ║ o: Var (name=arm) + ║ ╚══ Join (JoinIterator) (resultSizeActual=1.1K) [right] + ║ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=2.8K) [left] + ║ │ s: Var (name=arm) + ║ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + ║ │ o: Var (name=result) + ║ └── Filter (resultSizeActual=1.1K) [right] + ║ ╠══ Not + ║ ║ Exists + ║ ║ StatementPattern (resultSizeEstimate=3.6K, resultSizeActual=0) + ║ ║ s: Var (name=drug) + ║ ║ p: Var (name=_const_e46c34a6_uri, value=http://example.com/theme/pharma/indicatedFor, anonymous) + ║ ║ o: Var (name=disease) + ║ ╚══ Join (JoinIterator) (resultSizeActual=1.1K) + ║ ├── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) [left] + ║ │ s: Var (name=trial) + ║ │ p: Var (name=_const_5a7b59fd_uri, value=http://example.com/theme/pharma/studiesDisease, anonymous) + ║ │ o: Var (name=disease) + ║ └── Join (JoinIterator) (resultSizeActual=1.1K) [right] + ║ ╠══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) [left] + ║ ║ s: Var (name=arm) + ║ ║ p: Var (name=_const_aefd3274_uri, value=http://example.com/theme/pharma/armDrug, anonymous) + ║ ║ o: Var (name=drug) + ║ ╚══ Filter (resultSizeActual=1.1K) [right] + ║ ├── Compare (>) + ║ │ Var (name=rate) + ║ │ ValueConstant (value="0.6"^^) + ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) + ║ s: Var (name=result) + ║ p: Var (name=_const_d84fe169_uri, value=http://example.com/theme/pharma/responseRate, anonymous) + ║ o: Var (name=rate) + ╚══ Extension (resultSizeActual=2.2K) [right] + ├── StatementPattern (resultSizeEstimate=1.97, resultSizeActual=2.2K) + │ s: Var (name=drug) + │ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ o: Var (name=target) + └── ExtensionElem (optTarget) + Var (name=target) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-4.txt new file mode 100644 index 00000000000..4f9396f162b --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-4.txt @@ -0,0 +1,68 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=11.8K) + │ ║ ├── Filter (resultSizeActual=11.9K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optClassName) + │ ║ │ ║ ValueConstant (value="") + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=11.9K) + │ ║ │ ├── Union (resultSizeActual=11.9K) [left] + │ ║ │ │ ╠══ Join (JoinIterator) (resultSizeActual=10.0K) + │ ║ │ │ ║ ├── StatementPattern (costEstimate=977, resultSizeEstimate=2.0K, resultSizeActual=5.0K) [left] + │ ║ │ │ ║ │ s: Var (name=drug) + │ ║ │ │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ │ o: Var (name=_const_f6bbe068_uri, value=http://example.com/theme/pharma/Drug, anonymous) + │ ║ │ │ ║ └── Join (JoinIterator) (resultSizeActual=10.0K) [right] + │ ║ │ │ ║ ╠══ StatementPattern (costEstimate=1.32, resultSizeEstimate=2.02, resultSizeActual=10.0K) [left] + │ ║ │ │ ║ ║ s: Var (name=drug) + │ ║ │ │ ║ ║ p: Var (name=_const_fb60ad98_uri, value=http://example.com/theme/pharma/hasMolecule, anonymous) + │ ║ │ │ ║ ║ o: Var (name=mol) + │ ║ │ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=10.0K) [right] + │ ║ │ │ ║ s: Var (name=mol) + │ ║ │ │ ║ p: Var (name=_const_4d1dbdab_uri, value=http://example.com/theme/pharma/inClass, anonymous) + │ ║ │ │ ║ o: Var (name=class) + │ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=1.9K) + │ ║ │ │ ├── StatementPattern (costEstimate=318.2K, resultSizeEstimate=914, resultSizeActual=949) [left] + │ ║ │ │ │ s: Var (name=combo) + │ ║ │ │ │ p: Var (name=_const_94a74d5e_uri, value=http://example.com/theme/pharma/combinationOf, anonymous) + │ ║ │ │ │ o: Var (name=drug) + │ ║ │ │ └── Join (JoinIterator) (resultSizeActual=1.9K) [right] + │ ║ │ │ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.58, resultSizeActual=949) [left] + │ ║ │ │ ║ s: Var (name=combo) + │ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ o: Var (name=_const_a4089907_uri, value=http://example.com/theme/pharma/Combination, anonymous) + │ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=1.9K) [right] + │ ║ │ │ ├── StatementPattern (costEstimate=1.32, resultSizeEstimate=2.02, resultSizeActual=1.9K) [left] + │ ║ │ │ │ s: Var (name=drug) + │ ║ │ │ │ p: Var (name=_const_fb60ad98_uri, value=http://example.com/theme/pharma/hasMolecule, anonymous) + │ ║ │ │ │ o: Var (name=mol) + │ ║ │ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=1.9K) [right] + │ ║ │ │ s: Var (name=mol) + │ ║ │ │ p: Var (name=_const_4d1dbdab_uri, value=http://example.com/theme/pharma/inClass, anonymous) + │ ║ │ │ o: Var (name=class) + │ ║ │ └── Extension (resultSizeActual=11.9K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=11.9K) + │ ║ │ ║ s: Var (name=class) + │ ║ │ ║ p: Var (name=_const_f6ceb733_uri, value=http://example.com/theme/pharma/name, anonymous) + │ ║ │ ║ o: Var (name=optName) + │ ║ │ ╚══ ExtensionElem (optClassName) + │ ║ │ Var (name=optName) + │ ║ └── Filter (new scope) (resultSizeActual=34) + │ ║ ╠══ ListMemberOperator + │ ║ ║ Var (name=disease) + │ ║ ║ ValueConstant (value=http://example.com/theme/pharma/disease/4) + │ ║ ║ ValueConstant (value=http://example.com/theme/pharma/disease/5) + │ ║ ╚══ StatementPattern (resultSizeEstimate=5.0K, resultSizeActual=5.0K) + │ ║ s: Var (name=drug) + │ ║ p: Var (name=_const_28b88607_uri, value=http://example.com/theme/pharma/contraindicatedFor, anonymous) + │ ║ o: Var (name=disease) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (count) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-5.txt new file mode 100644 index 00000000000..53c98a3792f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-5.txt @@ -0,0 +1,58 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=32) + │ ║ ├── Compare (>) + │ ║ │ Var (name=optEffect) + │ ║ │ ValueConstant (value="0.3"^^) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=44) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=44) [left] + │ ║ ║ ├── BindingSetAssignment ([[marker=http://example.com/theme/pharma/biomarker/0], [marker=http://example.com/theme/pharma/biomarker/1], [marker=http://example.com/theme/pharma/biomarker/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=3) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=44) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.87, resultSizeEstimate=26, resultSizeActual=79) [left] + │ ║ ║ ║ s: Var (name=result) + │ ║ ║ ║ p: Var (name=_const_80a6979a_uri, value=http://example.com/theme/pharma/biomarker, anonymous) + │ ║ ║ ║ o: Var (name=marker) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=44) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=79) [left] + │ ║ ║ │ s: Var (name=arm) + │ ║ ║ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + │ ║ ║ │ o: Var (name=result) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=44) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=79) [left] + │ ║ ║ ║ s: Var (name=trial) + │ ║ ║ ║ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ ║ ║ o: Var (name=arm) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=44) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.67, resultSizeActual=79) [left] + │ ║ ║ │ s: Var (name=trial) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_4795bbfb_uri, value=http://example.com/theme/pharma/ClinicalTrial, anonymous) + │ ║ ║ └── Filter (resultSizeActual=44) [right] + │ ║ ║ ╠══ Or + │ ║ ║ ║ ├── Compare (<) + │ ║ ║ ║ │ Var (name=p) + │ ║ ║ ║ │ ValueConstant (value="0.05"^^) + │ ║ ║ ║ └── Compare (=) + │ ║ ║ ║ Var (name=p) + │ ║ ║ ║ ValueConstant (value="0.05"^^) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=79) + │ ║ ║ s: Var (name=result) + │ ║ ║ p: Var (name=_const_80c71989_uri, value=http://example.com/theme/pharma/pValue, anonymous) + │ ║ ║ o: Var (name=p) + │ ║ ╚══ Extension (resultSizeActual=44) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=44) + │ ║ │ s: Var (name=result) + │ ║ │ p: Var (name=_const_6999fbda_uri, value=http://example.com/theme/pharma/effectSize, anonymous) + │ ║ │ o: Var (name=effect) + │ ║ └── ExtensionElem (optEffect) + │ ║ Var (name=effect) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=trial) + └── ExtensionElem (count) + Count (Distinct) + Var (name=trial) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-6.txt new file mode 100644 index 00000000000..cd5a2f0284a --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-6.txt @@ -0,0 +1,70 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "combo" +║ ProjectionElem "sharedTargets" +╚══ Extension (resultSizeActual=1) + ├── Extension (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=1) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="1"^^) + │ ║ └── Group (combo) (resultSizeActual=5) + │ ║ Filter (resultSizeActual=22) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=3.3K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=drugB) + │ ║ │ ║ p: Var (name=_const_72f8dc5a_uri, value=http://example.com/theme/pharma/hasSideEffect, anonymous) + │ ║ │ ║ o: Var (name=sideEffect2) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optSideEffect) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/side-effect/0) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=22) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=12) [left] + │ ║ ║ ├── StatementPattern (costEstimate=238, resultSizeEstimate=948, resultSizeActual=949) [left] + │ ║ ║ │ s: Var (name=combo) + │ ║ ║ │ p: Var (name=_const_94a74d5e_uri, value=http://example.com/theme/pharma/combinationOf, anonymous) + │ ║ ║ │ o: Var (name=drugA) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=12) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.71, resultSizeActual=949) [left] + │ ║ ║ ║ s: Var (name=combo) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_a4089907_uri, value=http://example.com/theme/pharma/Combination, anonymous) + │ ║ ║ ╚══ Filter (resultSizeActual=12) [right] + │ ║ ║ ├── Compare (!=) + │ ║ ║ │ Var (name=drugA) + │ ║ ║ │ Var (name=drugB) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=1.9K) + │ ║ ║ ╠══ StatementPattern (costEstimate=1.32, resultSizeEstimate=1.97, resultSizeActual=1.9K) [left] + │ ║ ║ ║ s: Var (name=drugA) + │ ║ ║ ║ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ ║ ║ ║ o: Var (name=target) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=1.9K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=1.32, resultSizeEstimate=2.24, resultSizeActual=4.4K) [left] + │ ║ ║ │ s: Var (name=combo) + │ ║ ║ │ p: Var (name=_const_94a74d5e_uri, value=http://example.com/theme/pharma/combinationOf, anonymous) + │ ║ ║ │ o: Var (name=drugB) + │ ║ ║ └── StatementPattern (costEstimate=0.50, resultSizeEstimate=0.43, resultSizeActual=1.9K) [right] + │ ║ ║ s: Var (name=drugB) + │ ║ ║ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ ║ ║ o: Var (name=target) + │ ║ ╚══ Extension (resultSizeActual=22) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=2.08, resultSizeActual=22) + │ ║ │ s: Var (name=drugA) + │ ║ │ p: Var (name=_const_72f8dc5a_uri, value=http://example.com/theme/pharma/hasSideEffect, anonymous) + │ ║ │ o: Var (name=sideEffect) + │ ║ └── ExtensionElem (optSideEffect) + │ ║ Var (name=sideEffect) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=target) + │ ║ GroupElem (sharedTargets) + │ ║ Count (Distinct) + │ ║ Var (name=target) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=target) + └── ExtensionElem (sharedTargets) + Count (Distinct) + Var (name=target) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-7.txt new file mode 100644 index 00000000000..694489929d7 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-7.txt @@ -0,0 +1,60 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=5.7K) + │ ║ ├── And + │ ║ │ ╠══ Not + │ ║ │ ║ Exists + │ ║ │ ║ Join (JoinIterator) (resultSizeActual=0) + │ ║ │ ║ ├── StatementPattern (costEstimate=710.9M, resultSizeEstimate=1.6K, resultSizeActual=5.7K) [left] + │ ║ │ ║ │ s: Var (name=arm) + │ ║ │ ║ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + │ ║ │ ║ │ o: Var (name=r) + │ ║ │ ║ └── Filter (resultSizeActual=0) [right] + │ ║ │ ║ ╠══ ListMemberOperator + │ ║ │ ║ ║ Var (name=p) + │ ║ │ ║ ║ ValueConstant (value="0.08"^^) + │ ║ │ ║ ║ ValueConstant (value="0.09"^^) + │ ║ │ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=5.7K) + │ ║ │ ║ s: Var (name=r) + │ ║ │ ║ p: Var (name=_const_80c71989_uri, value=http://example.com/theme/pharma/pValue, anonymous) + │ ║ │ ║ o: Var (name=p) + │ ║ │ ╚══ Compare (!=) + │ ║ │ Var (name=optCompName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=5.7K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=5.7K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=932, resultSizeEstimate=2.8K, resultSizeActual=2.8K) [left] + │ ║ ║ │ s: Var (name=trial) + │ ║ ║ │ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ ║ │ o: Var (name=arm) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=5.7K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.73, resultSizeActual=2.8K) [left] + │ ║ ║ ║ s: Var (name=trial) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_4795bbfb_uri, value=http://example.com/theme/pharma/ClinicalTrial, anonymous) + │ ║ ║ ╚══ Union (resultSizeActual=5.7K) [right] + │ ║ ║ ├── StatementPattern (new scope) (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) + │ ║ ║ │ s: Var (name=arm) + │ ║ ║ │ p: Var (name=_const_4514e0aa_uri, value=http://example.com/theme/pharma/armComparator, anonymous) + │ ║ ║ │ o: Var (name=comp) + │ ║ ║ └── StatementPattern (new scope) (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) + │ ║ ║ s: Var (name=arm) + │ ║ ║ p: Var (name=_const_aefd3274_uri, value=http://example.com/theme/pharma/armDrug, anonymous) + │ ║ ║ o: Var (name=comp) + │ ║ ╚══ Extension (resultSizeActual=5.7K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=5.7K) + │ ║ │ s: Var (name=comp) + │ ║ │ p: Var (name=_const_f6ceb733_uri, value=http://example.com/theme/pharma/name, anonymous) + │ ║ │ o: Var (name=optName) + │ ║ └── ExtensionElem (optCompName) + │ ║ Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=arm) + └── ExtensionElem (count) + Count (Distinct) + Var (name=arm) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-8.txt new file mode 100644 index 00000000000..efc1701e407 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-8.txt @@ -0,0 +1,63 @@ +Projection (resultSizeActual=1.6K) +╠══ ProjectionElemList +║ ProjectionElem "drug" +║ ProjectionElem "targetCount" +╚══ Extension (resultSizeActual=1.6K) + ├── Extension (resultSizeActual=1.6K) + │ ╠══ Filter (resultSizeActual=1.6K) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="3"^^) + │ ║ └── Group (drug) (resultSizeActual=4.9K) + │ ║ Difference (resultSizeActual=19.8K) + │ ║ ├── Filter (resultSizeActual=19.9K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optMol) + │ ║ │ ║ ValueConstant (value=http://example.com/theme/pharma/molecule/0) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=19.9K) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=9.9K) [left] + │ ║ │ │ ╠══ StatementPattern (costEstimate=1.4K, resultSizeEstimate=2.6K, resultSizeActual=5.0K) [left] + │ ║ │ │ ║ s: Var (name=drug) + │ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ o: Var (name=_const_f6bbe068_uri, value=http://example.com/theme/pharma/Drug, anonymous) + │ ║ │ │ ╚══ StatementPattern (costEstimate=2.64, resultSizeEstimate=1.99, resultSizeActual=9.9K) [right] + │ ║ │ │ s: Var (name=drug) + │ ║ │ │ p: Var (name=_const_7f67635a_uri, value=http://example.com/theme/pharma/targets, anonymous) + │ ║ │ │ o: Var (name=target) + │ ║ │ └── Extension (resultSizeActual=19.9K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=2.01, resultSizeActual=19.9K) + │ ║ │ ║ s: Var (name=drug) + │ ║ │ ║ p: Var (name=_const_fb60ad98_uri, value=http://example.com/theme/pharma/hasMolecule, anonymous) + │ ║ │ ║ o: Var (name=mol) + │ ║ │ ╚══ ExtensionElem (optMol) + │ ║ │ Var (name=mol) + │ ║ └── Union (resultSizeActual=30) + │ ║ ╠══ Filter (resultSizeActual=16) + │ ║ ║ ├── SameTerm + │ ║ ║ │ Var (name=disease) + │ ║ ║ │ ValueConstant (value=http://example.com/theme/pharma/disease/6) + │ ║ ║ └── StatementPattern (resultSizeEstimate=15, resultSizeActual=16) + │ ║ ║ s: Var (name=drug) + │ ║ ║ p: Var (name=_const_28b88607_uri, value=http://example.com/theme/pharma/contraindicatedFor, anonymous) + │ ║ ║ o: Var (name=disease, value=http://example.com/theme/pharma/disease/6) + │ ║ ╚══ Filter (resultSizeActual=14) + │ ║ ├── SameTerm + │ ║ │ Var (name=disease) + │ ║ │ ValueConstant (value=http://example.com/theme/pharma/disease/7) + │ ║ └── StatementPattern (resultSizeEstimate=15, resultSizeActual=14) + │ ║ s: Var (name=drug) + │ ║ p: Var (name=_const_28b88607_uri, value=http://example.com/theme/pharma/contraindicatedFor, anonymous) + │ ║ o: Var (name=disease, value=http://example.com/theme/pharma/disease/7) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=target) + │ ║ GroupElem (targetCount) + │ ║ Count (Distinct) + │ ║ Var (name=target) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=target) + └── ExtensionElem (targetCount) + Count (Distinct) + Var (name=target) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-9.txt new file mode 100644 index 00000000000..da5b3a30a20 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/PHARMA/query-9.txt @@ -0,0 +1,90 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=13) + │ ║ ├── And + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=optDisease) + │ ║ │ ║ ValueConstant (value=http://example.com/theme/pharma/disease/8) + │ ║ │ ║ ValueConstant (value=http://example.com/theme/pharma/disease/9) + │ ║ │ ╚══ Exists + │ ║ │ StatementPattern (resultSizeEstimate=3.3K, resultSizeActual=0) + │ ║ │ s: Var (name=drug) + │ ║ │ p: Var (name=_const_72f8dc5a_uri, value=http://example.com/theme/pharma/hasSideEffect, anonymous) + │ ║ │ o: Var (name=se) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=2.2K) + │ ║ ╠══ Projection (new scope) (resultSizeActual=1.1K) [left] + │ ║ ║ ├── ProjectionElemList + │ ║ ║ │ ProjectionElem "drug" + │ ║ ║ │ ProjectionElem "avgEffect" + │ ║ ║ └── Extension (resultSizeActual=1.1K) + │ ║ ║ ╠══ Extension (resultSizeActual=1.1K) + │ ║ ║ ║ ├── Filter (resultSizeActual=1.1K) + │ ║ ║ ║ │ ╠══ Compare (>) + │ ║ ║ ║ │ ║ Var (name=_anon_having_1, anonymous) + │ ║ ║ ║ │ ║ ValueConstant (value="0.4"^^) + │ ║ ║ ║ │ ╚══ Group (drug) (resultSizeActual=1.8K) + │ ║ ║ ║ │ Filter (resultSizeActual=2.2K) + │ ║ ║ ║ │ ╠══ Compare (>) + │ ║ ║ ║ │ ║ Var (name=optRate) + │ ║ ║ ║ │ ║ ValueConstant (value="0.2"^^) + │ ║ ║ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=2.8K) + │ ║ ║ ║ │ ├── Join (JoinIterator) (resultSizeActual=2.8K) [left] + │ ║ ║ ║ │ │ ╠══ StatementPattern (costEstimate=917, resultSizeEstimate=2.8K, resultSizeActual=2.8K) [left] + │ ║ ║ ║ │ │ ║ s: Var (name=arm) + │ ║ ║ ║ │ │ ║ p: Var (name=_const_aefd3274_uri, value=http://example.com/theme/pharma/armDrug, anonymous) + │ ║ ║ ║ │ │ ║ o: Var (name=drug) + │ ║ ║ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=2.8K) [right] + │ ║ ║ ║ │ │ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=2.8K) [left] + │ ║ ║ ║ │ │ │ s: Var (name=trial) + │ ║ ║ ║ │ │ │ p: Var (name=_const_73c2e40a_uri, value=http://example.com/theme/pharma/hasArm, anonymous) + │ ║ ║ ║ │ │ │ o: Var (name=arm) + │ ║ ║ ║ │ │ └── Join (JoinIterator) (resultSizeActual=2.8K) [right] + │ ║ ║ ║ │ │ ╠══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.76, resultSizeActual=2.8K) [left] + │ ║ ║ ║ │ │ ║ s: Var (name=trial) + │ ║ ║ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ │ │ ║ o: Var (name=_const_4795bbfb_uri, value=http://example.com/theme/pharma/ClinicalTrial, anonymous) + │ ║ ║ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=2.8K) [right] + │ ║ ║ ║ │ │ ├── StatementPattern (costEstimate=1.22, resultSizeEstimate=1.00, resultSizeActual=2.8K) [left] + │ ║ ║ ║ │ │ │ s: Var (name=arm) + │ ║ ║ ║ │ │ │ p: Var (name=_const_60f6d7af_uri, value=http://example.com/theme/pharma/hasResult, anonymous) + │ ║ ║ ║ │ │ │ o: Var (name=result) + │ ║ ║ ║ │ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=2.8K) [right] + │ ║ ║ ║ │ │ s: Var (name=result) + │ ║ ║ ║ │ │ p: Var (name=_const_6999fbda_uri, value=http://example.com/theme/pharma/effectSize, anonymous) + │ ║ ║ ║ │ │ o: Var (name=effect) + │ ║ ║ ║ │ └── Extension (resultSizeActual=2.8K) [right] + │ ║ ║ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=2.8K) + │ ║ ║ ║ │ ║ s: Var (name=result) + │ ║ ║ ║ │ ║ p: Var (name=_const_d84fe169_uri, value=http://example.com/theme/pharma/responseRate, anonymous) + │ ║ ║ ║ │ ║ o: Var (name=rate) + │ ║ ║ ║ │ ╚══ ExtensionElem (optRate) + │ ║ ║ ║ │ Var (name=rate) + │ ║ ║ ║ │ GroupElem (_anon_having_1) + │ ║ ║ ║ │ Avg + │ ║ ║ ║ │ Var (name=effect) + │ ║ ║ ║ │ GroupElem (avgEffect) + │ ║ ║ ║ │ Avg + │ ║ ║ ║ │ Var (name=effect) + │ ║ ║ ║ └── ExtensionElem (_anon_having_1) + │ ║ ║ ║ Avg + │ ║ ║ ║ Var (name=effect) + │ ║ ║ ╚══ ExtensionElem (avgEffect) + │ ║ ║ Avg + │ ║ ║ Var (name=effect) + │ ║ ╚══ Extension (resultSizeActual=2.2K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.99, resultSizeActual=2.2K) + │ ║ │ s: Var (name=drug) + │ ║ │ p: Var (name=_const_e46c34a6_uri, value=http://example.com/theme/pharma/indicatedFor, anonymous) + │ ║ │ o: Var (name=disease) + │ ║ └── ExtensionElem (optDisease) + │ ║ Var (name=disease) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=drug) + └── ExtensionElem (count) + Count (Distinct) + Var (name=drug) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-0.txt new file mode 100644 index 00000000000..93aa0669531 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-0.txt @@ -0,0 +1,42 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Extension (resultSizeActual=6) + │ ║ ├── Filter (resultSizeActual=6) + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=optName) + │ ║ │ ║ ValueConstant (value="user0") + │ ║ │ ║ ValueConstant (value="user1") + │ ║ │ ║ ValueConstant (value="user2") + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=6) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=6) [left] + │ ║ │ │ ╠══ BindingSetAssignment ([[v=http://example.com/theme/social/user/0], [v=http://example.com/theme/social/user/1], [v=http://example.com/theme/social/user/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=3) [left] + │ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ │ ├── Filter (resultSizeActual=33) [left] + │ ║ │ │ │ ╠══ Compare (!=) + │ ║ │ │ │ ║ Var (name=u) + │ ║ │ │ │ ║ Var (name=v) + │ ║ │ │ │ ╚══ StatementPattern (costEstimate=4.00, resultSizeEstimate=11, resultSizeActual=33) + │ ║ │ │ │ s: Var (name=u) + │ ║ │ │ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ │ │ o: Var (name=v) + │ ║ │ │ └── BindingSetAssignment ([[u=http://example.com/theme/social/user/0], [u=http://example.com/theme/social/user/1], [u=http://example.com/theme/social/user/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=6) [right] + │ ║ │ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=6) [right] + │ ║ │ s: Var (name=u) + │ ║ │ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ │ o: Var (name=optName) + │ ║ └── ExtensionElem (pair) + │ ║ FunctionCall (http://www.w3.org/2005/xpath-functions#concat) + │ ║ ├── Str + │ ║ │ Var (name=u) + │ ║ └── Str + │ ║ Var (name=v) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=pair) + └── ExtensionElem (count) + Count (Distinct) + Var (name=pair) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-1.txt new file mode 100644 index 00000000000..e073dd83793 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-1.txt @@ -0,0 +1,81 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=4) + │ ║ ├── Filter (resultSizeActual=4) + │ ║ │ ╠══ Exists + │ ║ │ ║ Filter (resultSizeActual=0) + │ ║ │ ║ ╠══ Or + │ ║ │ ║ ║ ├── Compare (=) + │ ║ │ ║ ║ │ Var (name=name) + │ ║ │ ║ ║ │ ValueConstant (value="user0") + │ ║ │ ║ ║ └── Compare (=) + │ ║ │ ║ ║ Var (name=name) + │ ║ │ ║ ║ ValueConstant (value="user1") + │ ║ │ ║ ╚══ StatementPattern (resultSizeEstimate=378, resultSizeActual=6) + │ ║ │ ║ s: Var (name=u1) + │ ║ │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ │ ║ o: Var (name=name) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=6) + │ ║ │ ├── Filter (resultSizeActual=143.7K) [left] + │ ║ │ │ ╠══ Compare (!=) + │ ║ │ │ ║ Var (name=u2) + │ ║ │ │ ║ Var (name=u3) + │ ║ │ │ ╚══ StatementPattern (costEstimate=19.9K, resultSizeEstimate=138.9K, resultSizeActual=143.7K) + │ ║ │ │ s: Var (name=u3) + │ ║ │ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ │ o: Var (name=u2) + │ ║ │ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=192) [left] + │ ║ │ ║ s: Var (name=u2) + │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ ║ o: Var (name=u3) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ├── Filter (resultSizeActual=2.3K) [left] + │ ║ │ │ ╠══ Compare (!=) + │ ║ │ │ ║ Var (name=u1) + │ ║ │ │ ║ Var (name=u3) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.04, resultSizeEstimate=12, resultSizeActual=2.3K) + │ ║ │ │ s: Var (name=u3) + │ ║ │ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ │ o: Var (name=u1) + │ ║ │ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=570) [left] + │ ║ │ ║ s: Var (name=u1) + │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ ║ o: Var (name=u3) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ├── Filter (resultSizeActual=378) [left] + │ ║ │ │ ╠══ Compare (!=) + │ ║ │ │ ║ Var (name=u1) + │ ║ │ │ ║ Var (name=u2) + │ ║ │ │ ╚══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=378) + │ ║ │ │ s: Var (name=u2) + │ ║ │ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ │ o: Var (name=u1) + │ ║ │ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ╠══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=378) [left] + │ ║ │ ║ s: Var (name=u1) + │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ │ ║ o: Var (name=u2) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ ├── BindingSetAssignment ([[u3=http://example.com/theme/social/user/0], [u3=http://example.com/theme/social/user/1], [u3=http://example.com/theme/social/user/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=18) [left] + │ ║ │ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ │ BindingSetAssignment ([[u2=http://example.com/theme/social/user/0], [u2=http://example.com/theme/social/user/1], [u2=http://example.com/theme/social/user/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=12) + │ ║ │ BindingSetAssignment ([[u1=http://example.com/theme/social/user/0], [u1=http://example.com/theme/social/user/1], [u1=http://example.com/theme/social/user/2]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=6) + │ ║ └── Extension (resultSizeActual=0) + │ ║ ╠══ StatementPattern (resultSizeEstimate=138.9K, resultSizeActual=0) + │ ║ ║ s: Var (name=u1) + │ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ o: Var (name=u1) + │ ║ ╚══ ExtensionElem (_anon_path_1) + │ ║ Var (name=u1) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=u1) + └── ExtensionElem (count) + Count (Distinct) + Var (name=u1) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-10.txt new file mode 100644 index 00000000000..32c130177be --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-10.txt @@ -0,0 +1,96 @@ +Timed out while retrieving explanation! Explanation may be incomplete! +You can change the timeout by setting .setMaxExecutionTime(...) on your query. + +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=0) + ├── Group () (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=12) + │ ║ ├── And + │ ║ │ ╠══ ListMemberOperator + │ ║ │ ║ Var (name=optName) + │ ║ │ ║ ValueConstant (value="user7") + │ ║ │ ║ ValueConstant (value="user8") + │ ║ │ ║ ValueConstant (value="user9") + │ ║ │ ║ ValueConstant (value="user10") + │ ║ │ ║ ValueConstant (value="user11") + │ ║ │ ╚══ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ ValueConstant (value="user7") + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="user8") + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=378, resultSizeActual=18) + │ ║ │ s: Var (name=a) + │ ║ │ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ │ o: Var (name=name) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=18) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=18) [left] + │ ║ ║ ├── StatementPattern (costEstimate=47.9K, resultSizeEstimate=143.7K, resultSizeActual=7.8K) [left] + │ ║ ║ │ s: Var (name=e) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=a) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=71.6K) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=d) + │ ║ ║ ║ │ Var (name=e) + │ ║ ║ ║ └── StatementPattern (costEstimate=1.89, resultSizeEstimate=9.22, resultSizeActual=71.6K) + │ ║ ║ ║ s: Var (name=d) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=e) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ├── Filter (resultSizeActual=649.7K) [left] + │ ║ ║ │ ╠══ Compare (!=) + │ ║ ║ │ ║ Var (name=c) + │ ║ ║ │ ║ Var (name=d) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=1.89, resultSizeEstimate=9.22, resultSizeActual=649.7K) + │ ║ ║ │ s: Var (name=c) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=d) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=5.9M) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=b) + │ ║ ║ ║ │ Var (name=c) + │ ║ ║ ║ └── StatementPattern (costEstimate=1.89, resultSizeEstimate=9.22, resultSizeActual=5.9M) + │ ║ ║ ║ s: Var (name=b) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=c) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ├── Filter (resultSizeActual=15.0K) [left] + │ ║ ║ │ ╠══ Compare (!=) + │ ║ ║ │ ║ Var (name=a) + │ ║ ║ │ ║ Var (name=b) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=15.0K) + │ ║ ║ │ s: Var (name=a) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=b) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ╠══ BindingSetAssignment ([[e=http://example.com/theme/social/user/7], [e=http://example.com/theme/social/user/8], [e=http://example.com/theme/social/user/9], [e=http://example.com/theme/social/user/10], [e=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=3.9K) [left] + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=18) [right] + │ ║ ║ ├── BindingSetAssignment ([[d=http://example.com/theme/social/user/7], [d=http://example.com/theme/social/user/8], [d=http://example.com/theme/social/user/9], [d=http://example.com/theme/social/user/10], [d=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=1.0K) [left] + │ ║ ║ └── Filter (resultSizeActual=18) [right] + │ ║ ║ ╠══ Compare (!=) + │ ║ ║ ║ Var (name=a) + │ ║ ║ ║ Var (name=c) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=30) + │ ║ ║ ├── BindingSetAssignment ([[c=http://example.com/theme/social/user/7], [c=http://example.com/theme/social/user/8], [c=http://example.com/theme/social/user/9], [c=http://example.com/theme/social/user/10], [c=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=313) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=30) [right] + │ ║ ║ BindingSetAssignment ([[b=http://example.com/theme/social/user/7], [b=http://example.com/theme/social/user/8], [b=http://example.com/theme/social/user/9], [b=http://example.com/theme/social/user/10], [b=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=113) + │ ║ ║ BindingSetAssignment ([[a=http://example.com/theme/social/user/7], [a=http://example.com/theme/social/user/8], [a=http://example.com/theme/social/user/9], [a=http://example.com/theme/social/user/10], [a=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=30) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=18) [right] + │ ║ s: Var (name=e) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=a) + └── ExtensionElem (count) + Count (Distinct) + Var (name=a) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-2.txt new file mode 100644 index 00000000000..8c328ec9a4f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-2.txt @@ -0,0 +1,39 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=6) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=6) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=6) [left] + │ ║ ║ ├── Filter (resultSizeActual=192) [left] + │ ║ ║ │ ╠══ And + │ ║ ║ │ ║ ├── Exists + │ ║ ║ │ ║ │ StatementPattern (resultSizeEstimate=143.6K, resultSizeActual=0) + │ ║ ║ │ ║ │ s: Var (name=v) + │ ║ ║ │ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ │ o: Var (name=u) + │ ║ ║ │ ║ └── Compare (!=) + │ ║ ║ │ ║ Var (name=u) + │ ║ ║ │ ║ Var (name=v) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=143.6K, resultSizeEstimate=143.6K, resultSizeActual=143.7K) + │ ║ ║ │ s: Var (name=u) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=v) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ ║ BindingSetAssignment ([[v=http://example.com/theme/social/user/3], [v=http://example.com/theme/social/user/4], [v=http://example.com/theme/social/user/5], [v=http://example.com/theme/social/user/6]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=18) + │ ║ ║ BindingSetAssignment ([[u=http://example.com/theme/social/user/3], [u=http://example.com/theme/social/user/4], [u=http://example.com/theme/social/user/5], [u=http://example.com/theme/social/user/6]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=6) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=6) [right] + │ ║ s: Var (name=v) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=u) + └── ExtensionElem (count) + Count (Distinct) + Var (name=u) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-3.txt new file mode 100644 index 00000000000..24e2d9c7595 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-3.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "u" +║ ProjectionElem "degree" +╚══ Extension (resultSizeActual=0) + ├── Extension (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="3"^^) + │ ║ └── Group (u) (resultSizeActual=3) + │ ║ Filter (resultSizeActual=6) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optAlias) + │ ║ │ ValueConstant (value="user3") + │ ║ │ ValueConstant (value="user4") + │ ║ │ ValueConstant (value="user5") + │ ║ │ ValueConstant (value="user6") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=6) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=6) [left] + │ ║ ║ ├── BindingSetAssignment ([[v=http://example.com/theme/social/user/3], [v=http://example.com/theme/social/user/4], [v=http://example.com/theme/social/user/5], [v=http://example.com/theme/social/user/6]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=4) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=6) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=57) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=u) + │ ║ ║ ║ │ Var (name=v) + │ ║ ║ ║ └── StatementPattern (costEstimate=4.14, resultSizeEstimate=12, resultSizeActual=57) + │ ║ ║ ║ s: Var (name=u) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=v) + │ ║ ║ ╚══ BindingSetAssignment ([[u=http://example.com/theme/social/user/3], [u=http://example.com/theme/social/user/4], [u=http://example.com/theme/social/user/5], [u=http://example.com/theme/social/user/6]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=6) [right] + │ ║ ╚══ Extension (resultSizeActual=6) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=6) + │ ║ │ s: Var (name=u) + │ ║ │ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ │ o: Var (name=optName) + │ ║ └── ExtensionElem (optAlias) + │ ║ Var (name=optName) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=v) + │ ║ GroupElem (degree) + │ ║ Count (Distinct) + │ ║ Var (name=v) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=v) + └── ExtensionElem (degree) + Count (Distinct) + Var (name=v) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-4.txt new file mode 100644 index 00000000000..62df70279f0 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-4.txt @@ -0,0 +1,43 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=9) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=9) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=9) [left] + │ ║ ║ ├── Filter (resultSizeActual=143.7K) [left] + │ ║ ║ │ ╠══ And + │ ║ ║ │ ║ ├── Not + │ ║ ║ │ ║ │ Exists + │ ║ ║ │ ║ │ Extension (resultSizeActual=0) + │ ║ ║ │ ║ │ ╠══ StatementPattern (resultSizeEstimate=143.6K, resultSizeActual=0) + │ ║ ║ │ ║ │ ║ s: Var (name=u) + │ ║ ║ │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ │ ║ o: Var (name=u) + │ ║ ║ │ ║ │ ╚══ ExtensionElem (_anon_path_1) + │ ║ ║ │ ║ │ Var (name=u) + │ ║ ║ │ ║ └── Compare (!=) + │ ║ ║ │ ║ Var (name=u) + │ ║ ║ │ ║ Var (name=v) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=143.6K, resultSizeEstimate=143.6K, resultSizeActual=143.7K) + │ ║ ║ │ s: Var (name=u) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=v) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=9) [right] + │ ║ ║ BindingSetAssignment ([[v=http://example.com/theme/social/user/7], [v=http://example.com/theme/social/user/8], [v=http://example.com/theme/social/user/9], [v=http://example.com/theme/social/user/10], [v=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=70) + │ ║ ║ BindingSetAssignment ([[u=http://example.com/theme/social/user/7], [u=http://example.com/theme/social/user/8], [u=http://example.com/theme/social/user/9], [u=http://example.com/theme/social/user/10], [u=http://example.com/theme/social/user/11]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=9) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=9) [right] + │ ║ s: Var (name=v) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=u) + └── ExtensionElem (count) + Count (Distinct) + Var (name=u) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-5.txt new file mode 100644 index 00000000000..12c0194df56 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-5.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=494) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="user7") + │ ║ │ ValueConstant (value="user8") + │ ║ │ ValueConstant (value="user9") + │ ║ │ ValueConstant (value="user10") + │ ║ │ ValueConstant (value="user11") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=494) + │ ║ ╠══ Union (new scope) (resultSizeActual=494) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=27) + │ ║ ║ │ ╠══ Extension (new scope) (resultSizeActual=192) [left] + │ ║ ║ │ ║ ├── Join (JoinIterator) (resultSizeActual=192) + │ ║ ║ │ ║ │ ╠══ StatementPattern (costEstimate=0.50, resultSizeEstimate=143.6K, resultSizeActual=143.7K) [left] + │ ║ ║ │ ║ │ ║ s: Var (name=v) + │ ║ ║ │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ │ ║ o: Var (name=u) + │ ║ ║ │ ║ │ ╚══ StatementPattern (costEstimate=31.9K, resultSizeEstimate=0.00, resultSizeActual=192) [right] + │ ║ ║ │ ║ │ s: Var (name=u) + │ ║ ║ │ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ │ o: Var (name=v) + │ ║ ║ │ ║ └── ExtensionElem (activity) + │ ║ ║ │ ║ Var (name=v) + │ ║ ║ │ ╚══ BindingSetAssignment ([[u=http://example.com/theme/social/user/7], [u=http://example.com/theme/social/user/8], [u=http://example.com/theme/social/user/9], [u=http://example.com/theme/social/user/10], [u=http://example.com/theme/social/user/11]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=27) [right] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=467) + │ ║ ║ ╠══ Extension (new scope) (resultSizeActual=1.4M) [left] + │ ║ ║ ║ ├── StatementPattern (resultSizeEstimate=1.4M, resultSizeActual=1.4M) + │ ║ ║ ║ │ s: Var (name=post) + │ ║ ║ ║ │ p: Var (name=_const_34211a22_uri, value=http://example.com/theme/social/authored, anonymous) + │ ║ ║ ║ │ o: Var (name=u) + │ ║ ║ ║ └── ExtensionElem (activity) + │ ║ ║ ║ Var (name=post) + │ ║ ║ ╚══ BindingSetAssignment ([[u=http://example.com/theme/social/user/7], [u=http://example.com/theme/social/user/8], [u=http://example.com/theme/social/user/9], [u=http://example.com/theme/social/user/10], [u=http://example.com/theme/social/user/11]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=467) [right] + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=494) [right] + │ ║ s: Var (name=u) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=activity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=activity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-6.txt new file mode 100644 index 00000000000..578ee660ff9 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-6.txt @@ -0,0 +1,45 @@ +Projection (resultSizeActual=0) +╠══ ProjectionElemList +║ ProjectionElem "u" +║ ProjectionElem "connections" +╚══ Extension (resultSizeActual=0) + ├── Extension (resultSizeActual=0) + │ ╠══ Filter (resultSizeActual=0) + │ ║ ├── Compare (>=) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="5"^^) + │ ║ └── Group (u) (resultSizeActual=5) + │ ║ Filter (resultSizeActual=20) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=20) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=20) [left] + │ ║ ║ ├── BindingSetAssignment ([[u=http://example.com/theme/social/user/12], [u=http://example.com/theme/social/user/13], [u=http://example.com/theme/social/user/14], [u=http://example.com/theme/social/user/15], [u=http://example.com/theme/social/user/16], [u=http://example.com/theme/social/user/17]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=6) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=20) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=92) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=u) + │ ║ ║ ║ │ Var (name=v) + │ ║ ║ ║ └── StatementPattern (costEstimate=4.49, resultSizeEstimate=15, resultSizeActual=92) + │ ║ ║ ║ s: Var (name=u) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=v) + │ ║ ║ ╚══ BindingSetAssignment ([[v=http://example.com/theme/social/user/12], [v=http://example.com/theme/social/user/13], [v=http://example.com/theme/social/user/14], [v=http://example.com/theme/social/user/15], [v=http://example.com/theme/social/user/16], [v=http://example.com/theme/social/user/17]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=20) [right] + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=20) [right] + │ ║ s: Var (name=u) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ║ GroupElem (_anon_having_1) + │ ║ Count (Distinct) + │ ║ Var (name=v) + │ ║ GroupElem (connections) + │ ║ Count (Distinct) + │ ║ Var (name=v) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count (Distinct) + │ Var (name=v) + └── ExtensionElem (connections) + Count (Distinct) + Var (name=v) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-7.txt new file mode 100644 index 00000000000..45114246732 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-7.txt @@ -0,0 +1,52 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=20) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="user12") + │ ║ │ ValueConstant (value="user13") + │ ║ │ ValueConstant (value="user14") + │ ║ │ ValueConstant (value="user15") + │ ║ │ ValueConstant (value="user16") + │ ║ │ ValueConstant (value="user17") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=20) + │ ║ ╠══ Difference (resultSizeActual=20) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=20) + │ ║ ║ │ ╠══ Filter (resultSizeActual=192) [left] + │ ║ ║ │ ║ ├── And + │ ║ ║ │ ║ │ ╠══ Exists + │ ║ ║ │ ║ │ ║ StatementPattern (resultSizeEstimate=143.6K, resultSizeActual=0) + │ ║ ║ │ ║ │ ║ s: Var (name=v) + │ ║ ║ │ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ │ ║ o: Var (name=u) + │ ║ ║ │ ║ │ ╚══ Compare (!=) + │ ║ ║ │ ║ │ Var (name=u) + │ ║ ║ │ ║ │ Var (name=v) + │ ║ ║ │ ║ └── StatementPattern (costEstimate=143.7K, resultSizeEstimate=143.6K, resultSizeActual=143.7K) + │ ║ ║ │ ║ s: Var (name=u) + │ ║ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ o: Var (name=v) + │ ║ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=20) [right] + │ ║ ║ │ BindingSetAssignment ([[v=http://example.com/theme/social/user/12], [v=http://example.com/theme/social/user/13], [v=http://example.com/theme/social/user/14], [v=http://example.com/theme/social/user/15], [v=http://example.com/theme/social/user/16], [v=http://example.com/theme/social/user/17]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=33) + │ ║ ║ │ BindingSetAssignment ([[u=http://example.com/theme/social/user/12], [u=http://example.com/theme/social/user/13], [u=http://example.com/theme/social/user/14], [u=http://example.com/theme/social/user/15], [u=http://example.com/theme/social/user/16], [u=http://example.com/theme/social/user/17]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=20) + │ ║ ║ └── Extension (resultSizeActual=0) + │ ║ ║ ╠══ StatementPattern (resultSizeEstimate=143.6K, resultSizeActual=0) + │ ║ ║ ║ s: Var (name=v) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=v) + │ ║ ║ ╚══ ExtensionElem (_anon_path_1) + │ ║ ║ Var (name=v) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=20) [right] + │ ║ s: Var (name=v) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=u) + └── ExtensionElem (count) + Count (Distinct) + Var (name=u) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-8.txt new file mode 100644 index 00000000000..7e970c1b248 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-8.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=18) + │ ║ ├── ListMemberOperator + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="user0") + │ ║ │ ValueConstant (value="user1") + │ ║ │ ValueConstant (value="user2") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=1.1K) + │ ║ ╠══ Extension (resultSizeActual=1.1K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=1.1K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=47.9K, resultSizeEstimate=143.7K, resultSizeActual=143.7K) [left] + │ ║ ║ │ ║ s: Var (name=b) + │ ║ ║ │ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ ║ o: Var (name=c) + │ ║ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=1.1K) [right] + │ ║ ║ │ ├── StatementPattern (costEstimate=1.97, resultSizeEstimate=10, resultSizeActual=1.2M) [left] + │ ║ ║ │ │ s: Var (name=c) + │ ║ ║ │ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ │ o: Var (name=a) + │ ║ ║ │ └── StatementPattern (costEstimate=0.50, resultSizeEstimate=0.00, resultSizeActual=1.1K) [right] + │ ║ ║ │ s: Var (name=a) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=b) + │ ║ ║ └── ExtensionElem (cycleStart) + │ ║ ║ Var (name=a) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=1.1K) [right] + │ ║ s: Var (name=a) + │ ║ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=a) + └── ExtensionElem (count) + Count (Distinct) + Var (name=a) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-9.txt new file mode 100644 index 00000000000..823d644ec1b --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/SOCIAL_MEDIA/query-9.txt @@ -0,0 +1,61 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=343) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optAlias) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=343) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=343) [left] + │ ║ ║ ├── BindingSetAssignment ([[b=http://example.com/theme/social/user/3], [b=http://example.com/theme/social/user/4], [b=http://example.com/theme/social/user/5], [b=http://example.com/theme/social/user/6]]) (costEstimate=0, resultSizeEstimate=1.00, resultSizeActual=4) [left] + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=343) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=61) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=b) + │ ║ ║ ║ │ Var (name=c) + │ ║ ║ ║ └── StatementPattern (costEstimate=1.94, resultSizeEstimate=10, resultSizeActual=61) + │ ║ ║ ║ s: Var (name=b) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=c) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=343) [right] + │ ║ ║ ├── Filter (resultSizeActual=630) [left] + │ ║ ║ │ ╠══ Compare (!=) + │ ║ ║ │ ║ Var (name=c) + │ ║ ║ │ ║ Var (name=d) + │ ║ ║ │ ╚══ StatementPattern (costEstimate=1.94, resultSizeEstimate=10, resultSizeActual=630) + │ ║ ║ │ s: Var (name=c) + │ ║ ║ │ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ │ o: Var (name=d) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=343) [right] + │ ║ ║ ╠══ Filter (resultSizeActual=6.1K) [left] + │ ║ ║ ║ ├── Compare (!=) + │ ║ ║ ║ │ Var (name=d) + │ ║ ║ ║ │ Var (name=a) + │ ║ ║ ║ └── StatementPattern (costEstimate=1.94, resultSizeEstimate=10, resultSizeActual=6.1K) + │ ║ ║ ║ s: Var (name=d) + │ ║ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ ║ o: Var (name=a) + │ ║ ║ ╚══ Filter (resultSizeActual=343) [right] + │ ║ ║ ├── Compare (!=) + │ ║ ║ │ Var (name=a) + │ ║ ║ │ Var (name=b) + │ ║ ║ └── StatementPattern (costEstimate=0.50, resultSizeEstimate=0.01, resultSizeActual=343) + │ ║ ║ s: Var (name=a) + │ ║ ║ p: Var (name=_const_9c68e12a_uri, value=http://example.com/theme/social/follows, anonymous) + │ ║ ║ o: Var (name=b) + │ ║ ╚══ Extension (resultSizeActual=343) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=343) + │ ║ │ s: Var (name=b) + │ ║ │ p: Var (name=_const_7d17b943_uri, value=http://example.com/theme/social/name, anonymous) + │ ║ │ o: Var (name=optName) + │ ║ └── ExtensionElem (optAlias) + │ ║ Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=a) + └── ExtensionElem (count) + Count (Distinct) + Var (name=a) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-0.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-0.txt new file mode 100644 index 00000000000..d86368ddb76 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-0.txt @@ -0,0 +1,33 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=18.0K) + │ ║ ├── Filter (resultSizeActual=18.0K) [left] + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=optTime) + │ ║ │ ║ ValueConstant (value="08:00:00"^^) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=25.8K) + │ ║ │ ├── StatementPattern (resultSizeEstimate=8.6K, resultSizeActual=8.6K) [left] + │ ║ │ │ s: Var (name=service) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_a703e3e_uri, value=http://example.com/theme/train/TrainService, anonymous) + │ ║ │ └── Extension (resultSizeActual=25.8K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=3.00, resultSizeActual=25.8K) + │ ║ │ ║ s: Var (name=service) + │ ║ │ ║ p: Var (name=_const_4f78e4a9_uri, value=http://example.com/theme/train/scheduledTime, anonymous) + │ ║ │ ║ o: Var (name=time) + │ ║ │ ╚══ ExtensionElem (optTime) + │ ║ │ Var (name=time) + │ ║ └── StatementPattern (resultSizeEstimate=1.00, resultSizeActual=18.0K) [right] + │ ║ s: Var (name=service) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=service) + └── ExtensionElem (count) + Count (Distinct) + Var (name=service) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-1.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-1.txt new file mode 100644 index 00000000000..bf71ce58e89 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-1.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ LeftJoin (LeftJoinIterator) (resultSizeActual=4) + │ ║ ├── Filter (resultSizeActual=4) [left] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ Var (name=target) + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="OP 3") + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=76.7K) + │ ║ │ ├── BindingSetAssignment ([[target="OP 1"], [target="OP 2"]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=2) [left] + │ ║ │ └── Union (resultSizeActual=76.7K) [right] + │ ║ │ ╠══ Join (JoinIterator) (resultSizeActual=59.7K) + │ ║ │ ║ ├── StatementPattern (costEstimate=45.6K, resultSizeEstimate=15.4K, resultSizeActual=59.7K) [left] + │ ║ │ ║ │ s: Var (name=entity) + │ ║ │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ │ o: Var (name=_const_9807bf0f_uri, value=http://example.com/theme/train/OperationalPoint, anonymous) + │ ║ │ ║ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=59.7K) [right] + │ ║ │ ║ s: Var (name=entity) + │ ║ │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ │ ║ o: Var (name=name) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=16.9K) + │ ║ │ ├── StatementPattern (costEstimate=348.5M, resultSizeEstimate=15.4K, resultSizeActual=16.9K) [left] + │ ║ │ │ s: Var (name=entity) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_cef39ba5_uri, value=http://example.com/theme/train/Line, anonymous) + │ ║ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=16.9K) [right] + │ ║ │ s: Var (name=entity) + │ ║ │ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ │ o: Var (name=name) + │ ║ └── StatementPattern (resultSizeEstimate=45.6K, resultSizeActual=0) [right] + │ ║ s: Var (name=entity) + │ ║ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ o: Var (name=op) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=entity) + └── ExtensionElem (count) + Count (Distinct) + Var (name=entity) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-10.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-10.txt new file mode 100644 index 00000000000..d0b57e515b4 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-10.txt @@ -0,0 +1,49 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=176.2K) + │ ║ ├── Filter (resultSizeActual=269.5K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optSection) + │ ║ │ ║ Var (name=op) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=269.5K) + │ ║ │ ├── Union (resultSizeActual=59.7K) [left] + │ ║ │ │ ╠══ StatementPattern (new scope) (resultSizeEstimate=12.8K, resultSizeActual=29.8K) + │ ║ │ │ ║ s: Var (name=op) + │ ║ │ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ ║ o: Var (name=_const_9807bf0f_uri, value=http://example.com/theme/train/OperationalPoint, anonymous) + │ ║ │ │ ╚══ Join (JoinIterator) (resultSizeActual=29.8K) + │ ║ │ │ ├── StatementPattern (costEstimate=6.4K, resultSizeEstimate=12.8K, resultSizeActual=29.8K) [left] + │ ║ │ │ │ s: Var (name=op) + │ ║ │ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ │ o: Var (name=_const_9807bf0f_uri, value=http://example.com/theme/train/OperationalPoint, anonymous) + │ ║ │ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=29.8K) [right] + │ ║ │ │ s: Var (name=op) + │ ║ │ │ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ │ │ o: Var (name=name) + │ ║ │ └── Extension (resultSizeActual=269.5K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=4.51, resultSizeActual=269.5K) + │ ║ │ ║ s: Var (name=section) + │ ║ │ ║ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ │ ║ o: Var (name=op) + │ ║ │ ╚══ ExtensionElem (optSection) + │ ║ │ Var (name=section) + │ ║ └── Filter (new scope) (resultSizeActual=11.1K) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=name2) + │ ║ ║ └── ValueConstant (value="op 1") + │ ║ ╚══ StatementPattern (resultSizeEstimate=46.9K, resultSizeActual=46.9K) + │ ║ s: Var (name=op) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=name2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=op) + └── ExtensionElem (count) + Count (Distinct) + Var (name=op) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-2.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-2.txt new file mode 100644 index 00000000000..baf2cb6ebc6 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-2.txt @@ -0,0 +1,44 @@ +Projection (resultSizeActual=3) +╠══ ProjectionElemList +║ ProjectionElem "line" +║ ProjectionElem "sectionCount" +╚══ Extension (resultSizeActual=3) + ├── Extension (resultSizeActual=3) + │ ╠══ Filter (resultSizeActual=3) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (line) (resultSizeActual=3) + │ ║ LeftJoin (LeftJoinIterator) (resultSizeActual=26) + │ ║ ├── Join (JoinIterator) (resultSizeActual=3) [left] + │ ║ │ ╠══ StatementPattern (costEstimate=5.7K, resultSizeEstimate=11.8K, resultSizeActual=8.4K) [left] + │ ║ │ ║ s: Var (name=line) + │ ║ │ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ ║ o: Var (name=_const_cef39ba5_uri, value=http://example.com/theme/train/Line, anonymous) + │ ║ │ ╚══ Filter (resultSizeActual=3) [right] + │ ║ │ ├── ListMemberOperator + │ ║ │ │ Var (name=lineName) + │ ║ │ │ ValueConstant (value="Line 0") + │ ║ │ │ ValueConstant (value="Line 1") + │ ║ │ │ ValueConstant (value="Line 2") + │ ║ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=8.4K) + │ ║ │ s: Var (name=line) + │ ║ │ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ │ o: Var (name=lineName) + │ ║ └── StatementPattern (resultSizeEstimate=8.67, resultSizeActual=26) [right] + │ ║ s: Var (name=section) + │ ║ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ o: Var (name=line) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=section) + │ ║ GroupElem (sectionCount) + │ ║ Count (Distinct) + │ ║ Var (name=section) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=section) + └── ExtensionElem (sectionCount) + Count (Distinct) + Var (name=section) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-3.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-3.txt new file mode 100644 index 00000000000..4813e36417e --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-3.txt @@ -0,0 +1,43 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=67.3K) + │ ║ ├── Filter (resultSizeActual=67.3K) + │ ║ │ ╠══ Compare (!=) + │ ║ │ ║ Var (name=optTrack) + │ ║ │ ║ Var (name=section) + │ ║ │ ╚══ LeftJoin (LeftJoinIterator) (resultSizeActual=67.3K) + │ ║ │ ├── Join (JoinIterator) (resultSizeActual=67.3K) [left] + │ ║ │ │ ╠══ StatementPattern (costEstimate=31.8K, resultSizeEstimate=64.7K, resultSizeActual=67.3K) [left] + │ ║ │ │ ║ s: Var (name=section) + │ ║ │ │ ║ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ │ │ ║ o: Var (name=line) + │ ║ │ │ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.98, resultSizeActual=67.3K) [right] + │ ║ │ │ s: Var (name=section) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_b0bb051f_uri, value=http://example.com/theme/train/SectionOfLine, anonymous) + │ ║ │ └── Extension (resultSizeActual=67.3K) [right] + │ ║ │ ╠══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=67.3K) + │ ║ │ ║ s: Var (name=section) + │ ║ │ ║ p: Var (name=_const_5289cea3_uri, value=http://example.com/theme/train/hasTrackSection, anonymous) + │ ║ │ ║ o: Var (name=track) + │ ║ │ ╚══ ExtensionElem (optTrack) + │ ║ │ Var (name=track) + │ ║ └── Filter (new scope) (resultSizeActual=1) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── Str + │ ║ ║ │ Var (name=name) + │ ║ ║ └── ValueConstant (value="Line 0") + │ ║ ╚══ StatementPattern (resultSizeEstimate=45.2K, resultSizeActual=46.9K) + │ ║ s: Var (name=line) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=name) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=section) + └── ExtensionElem (count) + Count (Distinct) + Var (name=section) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-4.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-4.txt new file mode 100644 index 00000000000..90a6cfb256f --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-4.txt @@ -0,0 +1,40 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=36) + │ ║ ├── Exists + │ ║ │ StatementPattern (resultSizeEstimate=66.6K, resultSizeActual=0) + │ ║ │ s: Var (name=section) + │ ║ │ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ │ o: Var (name=line) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=269.5K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=2) [left] + │ ║ ║ ├── StatementPattern (costEstimate=5.6K, resultSizeEstimate=11.3K, resultSizeActual=8.4K) [left] + │ ║ ║ │ s: Var (name=line) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_cef39ba5_uri, value=http://example.com/theme/train/Line, anonymous) + │ ║ ║ └── Filter (resultSizeActual=2) [right] + │ ║ ║ ╠══ Or + │ ║ ║ ║ ├── Compare (=) + │ ║ ║ ║ │ Var (name=name) + │ ║ ║ ║ │ ValueConstant (value="Line 1") + │ ║ ║ ║ └── Compare (=) + │ ║ ║ ║ Var (name=name) + │ ║ ║ ║ ValueConstant (value="Line 2") + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=8.4K) + │ ║ ║ s: Var (name=line) + │ ║ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ ║ o: Var (name=name) + │ ║ ╚══ StatementPattern (resultSizeEstimate=134.8K, resultSizeActual=269.5K) [right] + │ ║ s: Var (name=section) + │ ║ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ o: Var (name=op) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=line) + └── ExtensionElem (count) + Count (Distinct) + Var (name=line) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-5.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-5.txt new file mode 100644 index 00000000000..b7ff5cad25e --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-5.txt @@ -0,0 +1,39 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=24) + │ ║ ├── Not + │ ║ │ Exists + │ ║ │ Filter (resultSizeActual=0) + │ ║ │ ╠══ Compare (>) + │ ║ │ ║ Var (name=late) + │ ║ │ ║ Var (name=threshold) + │ ║ │ ╚══ StatementPattern (resultSizeEstimate=14.9K, resultSizeActual=213) + │ ║ │ s: Var (name=service) + │ ║ │ p: Var (name=_const_4f78e4a9_uri, value=http://example.com/theme/train/scheduledTime, anonymous) + │ ║ │ o: Var (name=late) + │ ║ └── Join (JoinIterator) (resultSizeActual=94) + │ ║ ╠══ BindingSetAssignment ([[threshold="10:00:00"^^]]) (costEstimate=6.00, resultSizeEstimate=1.00, resultSizeActual=1) [left] + │ ║ ╚══ Join (JoinIterator) (resultSizeActual=94) [right] + │ ║ ├── StatementPattern (costEstimate=33.5K, resultSizeEstimate=11.2K, resultSizeActual=8.6K) [left] + │ ║ │ s: Var (name=service) + │ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ o: Var (name=_const_a703e3e_uri, value=http://example.com/theme/train/TrainService, anonymous) + │ ║ └── Filter (resultSizeActual=94) [right] + │ ║ ╠══ ListMemberOperator + │ ║ ║ Var (name=time) + │ ║ ║ ValueConstant (value="08:00:00"^^) + │ ║ ║ ValueConstant (value="09:00:00"^^) + │ ║ ╚══ StatementPattern (costEstimate=2.83, resultSizeEstimate=3.00, resultSizeActual=25.8K) + │ ║ s: Var (name=service) + │ ║ p: Var (name=_const_4f78e4a9_uri, value=http://example.com/theme/train/scheduledTime, anonymous) + │ ║ o: Var (name=time) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=service) + └── ExtensionElem (count) + Count (Distinct) + Var (name=service) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-6.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-6.txt new file mode 100644 index 00000000000..51c5b62e93d --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-6.txt @@ -0,0 +1,52 @@ +Projection (resultSizeActual=7.8K) +╠══ ProjectionElemList +║ ProjectionElem "line" +║ ProjectionElem "serviceCount" +╚══ Extension (resultSizeActual=7.8K) + ├── Extension (resultSizeActual=7.8K) + │ ╠══ Filter (resultSizeActual=7.8K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (line) (resultSizeActual=8.4K) + │ ║ Filter (resultSizeActual=34.3K) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=34.3K) + │ ║ ╠══ Union (resultSizeActual=34.3K) [left] + │ ║ ║ ├── Join (JoinIterator) (resultSizeActual=25.8K) + │ ║ ║ │ ╠══ StatementPattern (costEstimate=8.5K, resultSizeEstimate=24.9K, resultSizeActual=25.8K) [left] + │ ║ ║ │ ║ s: Var (name=service) + │ ║ ║ │ ║ p: Var (name=_const_9993352d_uri, value=http://example.com/theme/train/runsOnSection, anonymous) + │ ║ ║ │ ║ o: Var (name=section) + │ ║ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=25.8K) [right] + │ ║ ║ │ ├── StatementPattern (costEstimate=1.00, resultSizeEstimate=0.99, resultSizeActual=25.8K) [left] + │ ║ ║ │ │ s: Var (name=service) + │ ║ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ │ o: Var (name=_const_a703e3e_uri, value=http://example.com/theme/train/TrainService, anonymous) + │ ║ ║ │ └── StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=25.8K) [right] + │ ║ ║ │ s: Var (name=section) + │ ║ ║ │ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ ║ │ o: Var (name=line) + │ ║ ║ └── StatementPattern (new scope) (resultSizeEstimate=10.7K, resultSizeActual=8.4K) + │ ║ ║ s: Var (name=line) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_cef39ba5_uri, value=http://example.com/theme/train/Line, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=34.3K) [right] + │ ║ s: Var (name=line) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=optName) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=service) + │ ║ GroupElem (serviceCount) + │ ║ Count (Distinct) + │ ║ Var (name=service) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=service) + └── ExtensionElem (serviceCount) + Count (Distinct) + Var (name=service) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-7.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-7.txt new file mode 100644 index 00000000000..75c2c7616d5 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-7.txt @@ -0,0 +1,46 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Difference (resultSizeActual=1) + │ ║ ├── Filter (resultSizeActual=1) + │ ║ │ ╠══ Exists + │ ║ │ ║ StatementPattern (resultSizeEstimate=9.5K, resultSizeActual=0) + │ ║ │ ║ s: Var (name=service) + │ ║ │ ║ p: Var (name=_const_b4130d5_uri, value=http://example.com/theme/train/passesThrough, anonymous) + │ ║ │ ║ o: Var (name=op) + │ ║ │ ╚══ Join (JoinIterator) (resultSizeActual=2) + │ ║ │ ├── StatementPattern (costEstimate=5.9K, resultSizeEstimate=11.8K, resultSizeActual=29.8K) [left] + │ ║ │ │ s: Var (name=op) + │ ║ │ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ │ │ o: Var (name=_const_9807bf0f_uri, value=http://example.com/theme/train/OperationalPoint, anonymous) + │ ║ │ └── Filter (resultSizeActual=2) [right] + │ ║ │ ╠══ Or + │ ║ │ ║ ├── Compare (=) + │ ║ │ ║ │ Var (name=name) + │ ║ │ ║ │ ValueConstant (value="OP 1") + │ ║ │ ║ └── Compare (=) + │ ║ │ ║ Var (name=name) + │ ║ │ ║ ValueConstant (value="OP 2") + │ ║ │ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=29.8K) + │ ║ │ s: Var (name=op) + │ ║ │ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ │ o: Var (name=name) + │ ║ └── Filter (new scope) (resultSizeActual=1) + │ ║ ╠══ FunctionCall (http://www.w3.org/2005/xpath-functions#contains) + │ ║ ║ ├── FunctionCall (http://www.w3.org/2005/xpath-functions#lower-case) + │ ║ ║ │ Str + │ ║ ║ │ Var (name=name2) + │ ║ ║ └── ValueConstant (value="op 0") + │ ║ ╚══ StatementPattern (resultSizeEstimate=46.4K, resultSizeActual=46.9K) + │ ║ s: Var (name=op) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=name2) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=op) + └── ExtensionElem (count) + Count (Distinct) + Var (name=op) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-8.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-8.txt new file mode 100644 index 00000000000..7c116d8ed9c --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-8.txt @@ -0,0 +1,57 @@ +Projection (resultSizeActual=1) +╠══ ProjectionElemList +║ ProjectionElem "count" +╚══ Extension (resultSizeActual=1) + ├── Group () (resultSizeActual=1) + │ ╠══ Filter (resultSizeActual=9) + │ ║ ├── And + │ ║ │ ╠══ Exists + │ ║ │ ║ Join (JoinIterator) (resultSizeActual=0) + │ ║ │ ║ ╠══ StatementPattern (costEstimate=1496.3M, resultSizeEstimate=134.8K, resultSizeActual=25.9K) [left] + │ ║ │ ║ ║ s: Var (name=s2) + │ ║ │ ║ ║ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ │ ║ ║ o: Var (name=op) + │ ║ │ ║ ╚══ StatementPattern (costEstimate=213, resultSizeEstimate=45.6K, resultSizeActual=25.8K) [right] + │ ║ │ ║ s: Var (name=s1) + │ ║ │ ║ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ │ ║ o: Var (name=op) + │ ║ │ ╚══ ListMemberOperator + │ ║ │ Var (name=optName) + │ ║ │ ValueConstant (value="Line 0") + │ ║ │ ValueConstant (value="Line 1") + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=25.8K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=25.8K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=22.2K, resultSizeEstimate=66.6K, resultSizeActual=67.3K) [left] + │ ║ ║ │ s: Var (name=s2) + │ ║ ║ │ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ ║ │ o: Var (name=line) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=25.8K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=1.86, resultSizeEstimate=8.79, resultSizeActual=592.7K) [left] + │ ║ ║ ║ s: Var (name=s1) + │ ║ ║ ║ p: Var (name=_const_8ba830f_uri, value=http://example.com/theme/train/partOfLine, anonymous) + │ ║ ║ ║ o: Var (name=line) + │ ║ ║ ╚══ Join (JoinIterator) (resultSizeActual=25.8K) [right] + │ ║ ║ ├── StatementPattern (costEstimate=0.77, resultSizeEstimate=0.38, resultSizeActual=227.7K) [left] + │ ║ ║ │ s: Var (name=service) + │ ║ ║ │ p: Var (name=_const_9993352d_uri, value=http://example.com/theme/train/runsOnSection, anonymous) + │ ║ ║ │ o: Var (name=s2) + │ ║ ║ └── Join (JoinIterator) (resultSizeActual=25.8K) [right] + │ ║ ║ ╠══ StatementPattern (costEstimate=0.50, resultSizeEstimate=0.11, resultSizeActual=25.8K) [left] + │ ║ ║ ║ s: Var (name=service) + │ ║ ║ ║ p: Var (name=_const_9993352d_uri, value=http://example.com/theme/train/runsOnSection, anonymous) + │ ║ ║ ║ o: Var (name=s1) + │ ║ ║ ╚══ StatementPattern (costEstimate=1.00, resultSizeEstimate=0.99, resultSizeActual=25.8K) [right] + │ ║ ║ s: Var (name=service) + │ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ o: Var (name=_const_a703e3e_uri, value=http://example.com/theme/train/TrainService, anonymous) + │ ║ ╚══ StatementPattern (resultSizeEstimate=1.00, resultSizeActual=25.8K) [right] + │ ║ s: Var (name=line) + │ ║ p: Var (name=_const_cf02f21c_uri, value=http://example.com/theme/train/name, anonymous) + │ ║ o: Var (name=optName) + │ ╚══ GroupElem (count) + │ Count (Distinct) + │ Var (name=service) + └── ExtensionElem (count) + Count (Distinct) + Var (name=service) + diff --git a/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-9.txt b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-9.txt new file mode 100644 index 00000000000..8a4b85d9f63 --- /dev/null +++ b/core/sail/lmdb/src/test/resources/expected-plans/TRAIN/query-9.txt @@ -0,0 +1,51 @@ +Projection (resultSizeActual=67.3K) +╠══ ProjectionElemList +║ ProjectionElem "section" +║ ProjectionElem "trackCount" +╚══ Extension (resultSizeActual=67.3K) + ├── Extension (resultSizeActual=67.3K) + │ ╠══ Filter (resultSizeActual=67.3K) + │ ║ ├── Compare (>) + │ ║ │ Var (name=_anon_having_1, anonymous) + │ ║ │ ValueConstant (value="0"^^) + │ ║ └── Group (section) (resultSizeActual=67.3K) + │ ║ Filter (resultSizeActual=134.7K) + │ ║ ├── Compare (!=) + │ ║ │ Var (name=optOp) + │ ║ │ Var (name=section) + │ ║ └── LeftJoin (LeftJoinIterator) (resultSizeActual=134.7K) + │ ║ ╠══ Join (JoinIterator) (resultSizeActual=67.3K) [left] + │ ║ ║ ├── StatementPattern (costEstimate=6.4K, resultSizeEstimate=12.8K, resultSizeActual=67.3K) [left] + │ ║ ║ │ s: Var (name=section) + │ ║ ║ │ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ │ o: Var (name=_const_b0bb051f_uri, value=http://example.com/theme/train/SectionOfLine, anonymous) + │ ║ ║ └── Filter (resultSizeActual=67.3K) [right] + │ ║ ║ ╠══ Exists + │ ║ ║ ║ StatementPattern (resultSizeEstimate=12.8K, resultSizeActual=0) + │ ║ ║ ║ s: Var (name=track) + │ ║ ║ ║ p: Var (name=_const_f5e5585a_uri, value=http://www.w3.org/1999/02/22-rdf-syntax-ns#type, anonymous) + │ ║ ║ ║ o: Var (name=_const_585dd5cb_uri, value=http://example.com/theme/train/TrackSection, anonymous) + │ ║ ║ ╚══ StatementPattern (costEstimate=2.45, resultSizeEstimate=1.00, resultSizeActual=67.3K) + │ ║ ║ s: Var (name=section) + │ ║ ║ p: Var (name=_const_5289cea3_uri, value=http://example.com/theme/train/hasTrackSection, anonymous) + │ ║ ║ o: Var (name=track) + │ ║ ╚══ Extension (resultSizeActual=134.7K) [right] + │ ║ ├── StatementPattern (resultSizeEstimate=1.79, resultSizeActual=134.7K) + │ ║ │ s: Var (name=section) + │ ║ │ p: Var (name=_const_26ff10d8_uri, value=http://example.com/theme/train/connectsOperationalPoint, anonymous) + │ ║ │ o: Var (name=op) + │ ║ └── ExtensionElem (optOp) + │ ║ Var (name=op) + │ ║ GroupElem (_anon_having_1) + │ ║ Count + │ ║ Var (name=track) + │ ║ GroupElem (trackCount) + │ ║ Count (Distinct) + │ ║ Var (name=track) + │ ╚══ ExtensionElem (_anon_having_1) + │ Count + │ Var (name=track) + └── ExtensionElem (trackCount) + Count (Distinct) + Var (name=track) + diff --git a/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemoryStore.java b/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemoryStore.java index 61670d6dfa3..1d11e2eff40 100644 --- a/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemoryStore.java +++ b/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemoryStore.java @@ -17,15 +17,19 @@ import org.eclipse.rdf4j.common.concurrent.locks.Lock; import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; import org.eclipse.rdf4j.query.algebra.evaluation.impl.DefaultEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; import org.eclipse.rdf4j.sail.NotifyingSailConnection; import org.eclipse.rdf4j.sail.SailChangedEvent; +import org.eclipse.rdf4j.sail.SailConnectionListener; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.SailDataset; import org.eclipse.rdf4j.sail.base.SailSink; @@ -364,7 +368,28 @@ public boolean isWritable() { @Override protected NotifyingSailConnection getConnectionInternal() throws SailException { - return new MemoryStoreConnection(this); + MemoryStoreConnection connection = new MemoryStoreConnection(this); + EvaluationStrategyFactory factory = getEvaluationStrategyFactory(); + if (factory instanceof LearningEvaluationStrategyFactory) { + JoinStatsProvider statsProvider = ((LearningEvaluationStrategyFactory) factory).getStatsProvider(); + connection.addConnectionListener(new SailConnectionListener() { + @Override + public void statementAdded(Statement statement) { + statsProvider.recordStatementsAdded(1); + } + + @Override + public void statementRemoved(Statement statement) { + // no-op + } + + @Override + public void statementAdded(Statement statement, boolean inferred) { + statsProvider.recordStatementsAdded(1); + } + }); + } + return connection; } @Override diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreEvaluationStrategyFactoryTest.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreEvaluationStrategyFactoryTest.java new file mode 100644 index 00000000000..7cd0d0a6153 --- /dev/null +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreEvaluationStrategyFactoryTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.memory; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.junit.jupiter.api.Test; + +class MemoryStoreEvaluationStrategyFactoryTest { + + @Test + void defaultsToStandardEvaluationStrategyFactory() { + MemoryStore store = new MemoryStore(); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + assertFalse(factory instanceof LearningEvaluationStrategyFactory, + "MemoryStore should not default to the learned join optimizer factory"); + } +} diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreLearningEvaluationDefaultTest.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreLearningEvaluationDefaultTest.java new file mode 100644 index 00000000000..762ecd2b66c --- /dev/null +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/MemoryStoreLearningEvaluationDefaultTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.memory; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.DefaultEvaluationStrategyFactory; +import org.junit.jupiter.api.Test; + +class MemoryStoreLearningEvaluationDefaultTest { + + @Test + void defaultsToDefaultEvaluationStrategyFactory() { + MemoryStore store = new MemoryStore(); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + assertInstanceOf(DefaultEvaluationStrategyFactory.class, factory, + "Expected MemoryStore to default to the standard evaluation strategy"); + } +} diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/MemoryThemeQueryRegressionTest.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/MemoryThemeQueryRegressionTest.java new file mode 100644 index 00000000000..a32cf2a0c8d --- /dev/null +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/MemoryThemeQueryRegressionTest.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.memory.benchmark; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; + +class MemoryThemeQueryRegressionTest { + + private static final Theme THEME = Theme.PHARMA; + private static final int ITERATIONS = 3; + + @Test + void pharmaQueryIndex8RepeatsCorrectly() throws IOException { + assertQueryCountRepeated(8); + } + + @Test + void pharmaQueryIndex10RepeatsCorrectly() throws IOException { + assertQueryCountRepeated(10); + } + + private void assertQueryCountRepeated(int queryIndex) throws IOException { + SailRepository repository = new SailRepository(new MemoryStore()); + repository.init(); + try { + loadData(repository); + String query = ThemeQueryCatalog.queryFor(THEME, queryIndex); + long expected = ThemeQueryCatalog.expectedCountFor(THEME, queryIndex); + for (int iteration = 1; iteration <= ITERATIONS; iteration++) { + long actual = executeCount(repository, query); + if (actual != expected) { + String plan = explain(repository, query); + fail("Unexpected count for theme " + THEME + " queryIndex " + queryIndex + + " on iteration " + iteration + ": expected " + expected + " but got " + actual + + "\nOptimized plan:\n" + plan); + } + } + } finally { + repository.shutDown(); + } + } + + private void loadData(SailRepository repository) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(THEME, inserter); + connection.commit(); + } + } + + private long executeCount(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection(); + TupleQueryResult result = connection.prepareTupleQuery(query).evaluate()) { + long count = 0L; + while (result.hasNext()) { + result.next(); + count++; + } + return count; + } + } + + private String explain(SailRepository repository, String query) { + try (SailRepositoryConnection connection = repository.getConnection()) { + return connection.prepareTupleQuery(query) + .explain(Explanation.Level.Optimized) + .toString(); + } + } +} diff --git a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/ThemeQueryBenchmark.java b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/ThemeQueryBenchmark.java index 61d13b4bb26..cc307edcd9a 100644 --- a/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/ThemeQueryBenchmark.java +++ b/core/sail/memory/src/test/java/org/eclipse/rdf4j/sail/memory/benchmark/ThemeQueryBenchmark.java @@ -46,10 +46,10 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; @State(Scope.Benchmark) -@Warmup(iterations = 2, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 3) +@Warmup(iterations = 1, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 30) @BenchmarkMode({ Mode.AverageTime }) @Fork(value = 1, jvmArgs = { "-Xms32G", "-Xmx32G" }) -@Measurement(iterations = 2, batchSize = 1, timeUnit = TimeUnit.MILLISECONDS, time = 100) +@Measurement(iterations = 1, batchSize = 1, timeUnit = TimeUnit.SECONDS, time = 5) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class ThemeQueryBenchmark { @@ -146,6 +146,7 @@ public void testQueryCounts() throws IOException { } @Test + @Disabled public void testQueryExplanation() throws IOException { String[] queryIndexes = paramValues("z_queryIndex"); String[] themeNames = paramValues("themeName"); diff --git a/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java b/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java index 6b3c77d94fa..3c0414fa7a7 100644 --- a/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java +++ b/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java @@ -31,15 +31,18 @@ import org.eclipse.rdf4j.common.io.MavenUtil; import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy; import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolver; import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; -import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.SailConnectionListener; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.SailSource; import org.eclipse.rdf4j.sail.base.SailStore; @@ -384,7 +387,7 @@ public boolean isWalEnabled() { */ public synchronized EvaluationStrategyFactory getEvaluationStrategyFactory() { if (evalStratFactory == null) { - evalStratFactory = new StrictEvaluationStrategyFactory(getFederatedServiceResolver()); + evalStratFactory = new LearningEvaluationStrategyFactory(getFederatedServiceResolver()); } evalStratFactory.setQuerySolutionCacheThreshold(getIterationCacheSyncThreshold()); evalStratFactory.setTrackResultSize(isTrackResultSize()); @@ -572,7 +575,28 @@ public boolean isWritable() { @Override protected NotifyingSailConnection getConnectionInternal() throws SailException { - return new NativeStoreConnection(this); + NativeStoreConnection connection = new NativeStoreConnection(this); + EvaluationStrategyFactory factory = getEvaluationStrategyFactory(); + if (factory instanceof LearningEvaluationStrategyFactory) { + JoinStatsProvider statsProvider = ((LearningEvaluationStrategyFactory) factory).getStatsProvider(); + connection.addConnectionListener(new SailConnectionListener() { + @Override + public void statementAdded(Statement statement) { + statsProvider.recordStatementsAdded(1); + } + + @Override + public void statementRemoved(Statement statement) { + // no-op + } + + @Override + public void statementAdded(Statement statement, boolean inferred) { + statsProvider.recordStatementsAdded(1); + } + }); + } + return connection; } @Override diff --git a/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/DpJoinOrderingIntegrationTest.java b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/DpJoinOrderingIntegrationTest.java new file mode 100644 index 00000000000..baa150ace6b --- /dev/null +++ b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/DpJoinOrderingIntegrationTest.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.nativerdf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.PatternKey; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DpJoinOrderingIntegrationTest { + + @TempDir + public File dataDir; + + @Test + void dpPlannerReducesTripleSourceCalls() { + File greedyDir = new File(dataDir, "greedy"); + File dpDir = new File(dataDir, "dp"); + + FixedJoinStats greedyStats = FixedJoinStats.forDefaults(); + FixedJoinStats dpStats = FixedJoinStats.forDefaults(); + + long greedyCalls = runQueryWithConfig(greedyDir, greedyStats, newConfig(false)); + long dpCalls = runQueryWithConfig(dpDir, dpStats, newConfig(true)); + + assertEquals(1101L, greedyCalls); + assertEquals(111L, dpCalls); + assertTrue(dpCalls < greedyCalls); + } + + private long runQueryWithConfig(File dir, FixedJoinStats statsProvider, LearnedJoinConfig config) { + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(statsProvider, null, config); + NativeStore store = new NativeStore(dir); + store.setEvaluationStrategyFactory(factory); + Repository repo = new SailRepository(store); + repo.init(); + + try (RepositoryConnection conn = repo.getConnection()) { + ValueFactory vf = conn.getValueFactory(); + IRI predA = vf.createIRI("http://example.org/aPred"); + IRI predB = vf.createIRI("http://example.org/bPred"); + IRI predC = vf.createIRI("http://example.org/cPred"); + loadData(conn, vf, predA, predB, predC); + + statsProvider.configure(predA, predB, predC); + + String query = "SELECT ?x ?y WHERE { " + + "?x \"hot\" . " + + "?x ?y . " + + "?y \"cold\" . " + + "}"; + + long callsBefore = statsProvider.getTotalCalls(); + int results = runQuery(conn, query); + long callsAfter = statsProvider.getTotalCalls(); + + assertEquals(10, results); + return callsAfter - callsBefore; + } finally { + repo.shutDown(); + } + } + + private static int runQuery(RepositoryConnection conn, String query) { + TupleQuery tupleQuery = conn.prepareTupleQuery(query); + int count = 0; + try (TupleQueryResult result = tupleQuery.evaluate()) { + while (result.hasNext()) { + result.next(); + count++; + } + } + return count; + } + + private static void loadData(RepositoryConnection conn, ValueFactory vf, IRI predA, IRI predB, IRI predC) { + for (int i = 1; i <= 1000; i++) { + IRI subj = vf.createIRI("http://example.org/x" + i); + conn.add(subj, predA, vf.createLiteral("hot")); + if (i <= 100) { + IRI obj = vf.createIRI("http://example.org/y" + i); + conn.add(subj, predB, obj); + if (i <= 10) { + conn.add(obj, predC, vf.createLiteral("cold")); + } + } + } + } + + private static LearnedJoinConfig newConfig(boolean enableDp) { + return new LearnedJoinConfig( + LearnedJoinConfig.DEFAULT_DP_THRESHOLD, + enableDp); + } + + private static final class FixedJoinStats implements JoinStatsProvider { + + private final AtomicLong totalCalls = new AtomicLong(); + private volatile Map averages; + + static FixedJoinStats forDefaults() { + FixedJoinStats stats = new FixedJoinStats(); + stats.averages = Map.of(); + return stats; + } + + void configure(IRI predA, IRI predB, IRI predC) { + Objects.requireNonNull(predA, "predA"); + Objects.requireNonNull(predB, "predB"); + Objects.requireNonNull(predC, "predC"); + averages = Map.of( + new PatternKey(predA, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 1.0d, + new PatternKey(predA, + PatternKey.SUBJECT_BOUND | PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), + 0.1d, + new PatternKey(predB, PatternKey.PREDICATE_BOUND), 100.0d, + new PatternKey(predB, PatternKey.SUBJECT_BOUND | PatternKey.PREDICATE_BOUND), 1000.0d, + new PatternKey(predB, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 1000.0d, + new PatternKey(predC, PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), 1000.0d, + new PatternKey(predC, + PatternKey.SUBJECT_BOUND | PatternKey.PREDICATE_BOUND | PatternKey.OBJECT_BOUND), + 1.0d); + } + + @Override + public void reset() { + averages = Map.of(); + totalCalls.set(0); + } + + @Override + public void recordCall(PatternKey key) { + totalCalls.incrementAndGet(); + } + + @Override + public void recordResults(PatternKey key, long resultCount) { + // ignore to keep averages fixed + } + + @Override + public void seedIfAbsent(PatternKey key, double defaultCardinality, long priorCalls) { + // ignore to keep averages fixed + } + + @Override + public double getAverageResults(PatternKey key) { + Double value = averages.get(key); + return value == null ? 0.0d : value; + } + + @Override + public boolean hasStats(PatternKey key) { + return averages.containsKey(key); + } + + @Override + public long getTotalCalls() { + return totalCalls.get(); + } + } +} diff --git a/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/LearningJoinOptimizerTest.java b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/LearningJoinOptimizerTest.java new file mode 100644 index 00000000000..4e58f4f7cf3 --- /dev/null +++ b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/LearningJoinOptimizerTest.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.nativerdf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.lang.reflect.Method; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.TupleQuery; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class LearningJoinOptimizerTest { + + private static final String FACTORY_CLASS = "org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory"; + + @TempDir + public File dataDir; + + @Test + public void learnsJoinOrderAcrossRuns() throws Exception { + EvaluationStrategyFactory factory = newLearningFactory(); + NativeStore store = new NativeStore(dataDir); + store.setEvaluationStrategyFactory(factory); + Repository repo = new SailRepository(store); + repo.init(); + + try (RepositoryConnection conn = repo.getConnection()) { + ValueFactory vf = conn.getValueFactory(); + IRI highPred = vf.createIRI("http://example.org/highCardPred"); + IRI lowPred = vf.createIRI("http://example.org/lowCardPred"); + String hotValue = "hot"; + + for (int i = 1; i <= 1000; i++) { + IRI subj = vf.createIRI("http://example.org/Subject" + i); + conn.add(subj, highPred, vf.createLiteral(hotValue)); + if (i <= 10) { + conn.add(subj, lowPred, vf.createLiteral("lowVal" + i)); + } + } + + String query = "SELECT ?s ?v2 WHERE { " + + "?s \"hot\" . " + + "?s ?v2 . " + + "}"; + + long callsBefore = getTotalCalls(factory); + int resultsFirst = runQuery(conn, query); + long callsAfterFirst = getTotalCalls(factory); + long callsFirst = callsAfterFirst - callsBefore; + + int resultsSecond = runQuery(conn, query); + long callsAfterSecond = getTotalCalls(factory); + long callsSecond = callsAfterSecond - callsAfterFirst; + + assertEquals(10, resultsFirst); + assertEquals(10, resultsSecond); + assertEquals(1001L, callsFirst); + assertEquals(11L, callsSecond); + } finally { + repo.shutDown(); + } + } + + private static int runQuery(RepositoryConnection conn, String query) { + TupleQuery tupleQuery = conn.prepareTupleQuery(query); + int count = 0; + try (TupleQueryResult result = tupleQuery.evaluate()) { + while (result.hasNext()) { + result.next(); + count++; + } + } + return count; + } + + private static EvaluationStrategyFactory newLearningFactory() { + try { + Class clazz = Class.forName(FACTORY_CLASS); + return (EvaluationStrategyFactory) clazz.getDeclaredConstructor(EvaluationStatistics.class) + .newInstance(new EvaluationStatistics()); + } catch (ClassNotFoundException e) { + fail("Missing LearningEvaluationStrategyFactory. Implement " + FACTORY_CLASS); + } catch (ReflectiveOperationException e) { + fail("Failed to instantiate LearningEvaluationStrategyFactory: " + e.getMessage()); + } + return null; + } + + private static long getTotalCalls(EvaluationStrategyFactory factory) { + try { + Method getStatsProvider = factory.getClass().getMethod("getStatsProvider"); + Object statsProvider = getStatsProvider.invoke(factory); + Method getTotalCalls = statsProvider.getClass().getMethod("getTotalCalls"); + Object total = getTotalCalls.invoke(statsProvider); + return ((Number) total).longValue(); + } catch (ReflectiveOperationException e) { + fail("Failed to read stats provider totals: " + e.getMessage()); + } + return -1; + } +} diff --git a/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreLearningEvaluationDefaultTest.java b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreLearningEvaluationDefaultTest.java new file mode 100644 index 00000000000..39e223a02e8 --- /dev/null +++ b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/NativeStoreLearningEvaluationDefaultTest.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +// Some portions generated by Codex +package org.eclipse.rdf4j.sail.nativerdf; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.io.File; + +import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class NativeStoreLearningEvaluationDefaultTest { + + @TempDir + File dataDir; + + @Test + void defaultsToLearningEvaluationStrategyFactory() { + NativeStore store = new NativeStore(dataDir); + EvaluationStrategyFactory factory = store.getEvaluationStrategyFactory(); + assertInstanceOf(LearningEvaluationStrategyFactory.class, factory, + "Expected NativeStore to default to the learned evaluation strategy"); + } +} diff --git a/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/benchmark/ThemeQueryBenchmark.java b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/benchmark/ThemeQueryBenchmark.java index 18ba7e8b358..c5882eb5716 100644 --- a/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/benchmark/ThemeQueryBenchmark.java +++ b/core/sail/nativerdf/src/test/java/org/eclipse/rdf4j/sail/nativerdf/benchmark/ThemeQueryBenchmark.java @@ -153,6 +153,7 @@ public void testQueryCounts() throws IOException { } @Test + @Disabled public void testQueryExplanation() throws IOException { String[] queryIndexes = paramValues("z_queryIndex"); String[] themeNames = paramValues("themeName"); diff --git a/scripts/plan-diff.py b/scripts/plan-diff.py new file mode 100644 index 00000000000..cf04ec710d3 --- /dev/null +++ b/scripts/plan-diff.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +"""Run and/or diff learned join plans from plan-evolution logs.""" + +from __future__ import annotations + +import argparse +import os +import re +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Dict, List, Tuple + +RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + +JAVA_TEMPLATE = """import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.assertj.core.util.Files; +import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator; +import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.query.explanation.Explanation; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.JoinStatsProvider; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.learned.LearnedJoinConfig; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig; + +public class PlanEvolution { + private static final String THEMES_PROP = "plan.themes"; + private static final String QUERY_INDEXES_PROP = "plan.queryIndexes"; + private static final String ITERATIONS_PROP = "plan.iterations"; + + public static void main(String[] args) throws Exception { + List themes = splitValues(System.getProperty(THEMES_PROP, "ELECTRICAL_GRID,PHARMA")); + List queryIndexes = splitIndexes(System.getProperty(QUERY_INDEXES_PROP, "4,6")); + int iterations = Integer.getInteger(ITERATIONS_PROP, 5); + for (String themeName : themes) { + for (int queryIndex : queryIndexes) { + runScenario(themeName, queryIndex, false, iterations); + runScenario(themeName, queryIndex, true, iterations); + } + } + } + + private static void runScenario(String themeName, int queryIndex, boolean dpEnabled, int iterations) + throws Exception { + System.setProperty(LearnedJoinConfig.DP_ENABLED_PROPERTY, Boolean.toString(dpEnabled)); + JoinStatsProvider statsProvider = new MemoryJoinStats(); + LearnedJoinConfig joinConfig = new LearnedJoinConfig(); + LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(statsProvider, null, joinConfig); + + File dataDir = Files.newTemporaryFolder(); + LmdbStoreConfig config = new LmdbStoreConfig("spoc,ospc,psoc"); + config.setForceSync(false); + config.setValueDBSize(1_073_741_824L); + config.setTripleDBSize(config.getValueDBSize()); + LmdbStore store = new LmdbStore(dataDir, config); + store.setEvaluationStrategyFactory(factory); + + SailRepository repository = new SailRepository(store); + Theme theme = Theme.valueOf(themeName); + String query = ThemeQueryCatalog.queryFor(theme, queryIndex); + loadData(repository, theme); + + System.out.println("=== theme=" + themeName + " queryIndex=" + queryIndex + " dpEnabled=" + dpEnabled + + " iterations=" + iterations + " ==="); + for (int iteration = 0; iteration < iterations; iteration++) { + try (SailRepositoryConnection connection = repository.getConnection()) { + String explanation = connection + .prepareTupleQuery(query) + .explain(Explanation.Level.Executed) + .toString(); + System.out.println("--- iteration=" + iteration + " ---"); + System.out.println(explanation); + } + } + + repository.shutDown(); + FileUtils.deleteDirectory(dataDir); + } + + private static void loadData(SailRepository repository, Theme theme) throws IOException { + try (SailRepositoryConnection connection = repository.getConnection()) { + connection.begin(IsolationLevels.NONE); + RDFInserter inserter = new RDFInserter(connection); + ThemeDataSetGenerator.generate(theme, inserter); + connection.commit(); + } + } + + private static List splitValues(String value) { + List values = new ArrayList<>(); + for (String part : value.split(",")) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + values.add(trimmed); + } + } + return values; + } + + private static List splitIndexes(String value) { + return splitValues(value).stream() + .map(Integer::parseInt) + .collect(Collectors.toList()); + } +} +""" + + +def compact_uri(uri: str) -> str: + if uri == RDF_TYPE: + return "rdf:type" + if "/theme/grid/" in uri: + return "grid:" + uri.rsplit("/", 1)[-1] + if "/theme/pharma/" in uri: + return "pharma:" + uri.rsplit("/", 1)[-1] + if "#" in uri: + return uri.rsplit("#", 1)[-1] + if "/" in uri: + return uri.rsplit("/", 1)[-1] + return uri + + +def parse_log(text: str) -> Dict[Tuple[str, int, str], Dict[int, str]]: + sections = re.split(r"(?m)^=== theme=", text) + data: Dict[Tuple[str, int, str], Dict[int, str]] = {} + for sec in sections: + if not sec.strip(): + continue + header, *rest = sec.split("\n", 1) + m = re.match(r"(\w+) queryIndex=(\d+) dpEnabled=(\w+) iterations=(\d+)", header.strip()) + if not m: + continue + theme, qidx, dp, _ = m.groups() + body = rest[0] if rest else "" + key = (theme, int(qidx), dp) + data[key] = {} + for m_it in re.finditer(r"(?m)^--- iteration=(\d+) ---", body): + it = int(m_it.group(1)) + start = m_it.end() + m_next = re.search(r"(?m)^--- iteration=\d+ ---", body[start:]) + end = start + (m_next.start() if m_next else len(body[start:])) + data[key][it] = body[start:end] + return data + + +def extract_patterns(block: str, max_patterns: int) -> List[str]: + lines = block.splitlines() + try: + idx = next(i for i, line in enumerate(lines) if "LeftJoin" in line) + except StopIteration: + return [] + patterns: List[str] = [] + pending_actual = None + for line in lines[idx + 1 :]: + if "StatementPattern" in line: + match = re.search(r"resultSizeActual=([^),]+)", line) + pending_actual = match.group(1) if match else "?" + continue + if pending_actual is not None and "p: Var" in line: + match = re.search(r"value=([^,]+)", line) + uri = match.group(1) if match else line.strip() + patterns.append(f"{compact_uri(uri)}@{pending_actual}") + pending_actual = None + if len(patterns) >= max_patterns: + break + return patterns + + +def diff_patterns(left: List[str], right: List[str]) -> str: + max_len = max(len(left), len(right)) + diffs = [] + for i in range(max_len): + lhs = left[i] if i < len(left) else "" + rhs = right[i] if i < len(right) else "" + if lhs != rhs: + diffs.append(f"{i+1}:{lhs} != {rhs}") + return "same" if not diffs else "; ".join(diffs) + + +def run_plan_evolution( + log: Path, + iterations: int, + themes: str, + query_indexes: str, + classpath_file: Path, + java_bin: str, + javac_bin: str, +) -> None: + classpath_file.parent.mkdir(parents=True, exist_ok=True) + if not classpath_file.exists(): + cmd = [ + "mvn", + "-o", + "-Dmaven.repo.local=.m2_repo", + "-pl", + "core/sail/lmdb", + "-DincludeScope=test", + f"-Dmdep.outputFile={classpath_file}", + "dependency:build-classpath", + ] + subprocess.run(cmd, check=True) + + java_source = Path(tempfile.gettempdir()) / "PlanEvolution.java" + java_source.write_text(JAVA_TEMPLATE) + compile_cp = f"{classpath_file.read_text().strip()}:core/sail/lmdb/target/classes" + subprocess.run([javac_bin, "-cp", compile_cp, str(java_source)], check=True) + + java_cp = f"{java_source.parent}:{classpath_file.read_text().strip()}:core/sail/lmdb/target/classes" + java_cmd = [ + java_bin, + f"-Dplan.iterations={iterations}", + f"-Dplan.themes={themes}", + f"-Dplan.queryIndexes={query_indexes}", + "-cp", + java_cp, + "PlanEvolution", + ] + + log.parent.mkdir(parents=True, exist_ok=True) + with log.open("w", encoding="utf-8") as handle: + proc = subprocess.Popen(java_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + assert proc.stdout is not None + for line in proc.stdout: + handle.write(line) + sys.stdout.write(line) + sys.stdout.flush() + ret = proc.wait() + if ret != 0: + raise subprocess.CalledProcessError(ret, java_cmd) + + try: + java_source.unlink() + (java_source.parent / "PlanEvolution.class").unlink() + except FileNotFoundError: + pass + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run/diff plans from plan-evolution logs") + parser.add_argument("log", nargs="?", type=Path, help="path to plan-evolution log") + parser.add_argument("--run", action="store_true", help="run plan evolution before diffing") + parser.add_argument("--output", type=Path, default=Path("/tmp/plan-evolution.log"), + help="output path for plan-evolution log") + parser.add_argument("--iterations", type=int, default=5, help="iterations per query") + parser.add_argument("--themes", default="ELECTRICAL_GRID,PHARMA", help="comma-separated themes") + parser.add_argument("--query-indexes", default="4,6", help="comma-separated query indexes (0-based)") + parser.add_argument("--classpath-file", type=Path, default=Path("/tmp/lmdb-test-cp.txt"), + help="path to store Maven test classpath") + parser.add_argument("--java", default=os.environ.get("JAVA_BIN", "java"), help="java binary") + parser.add_argument("--javac", default=os.environ.get("JAVAC_BIN", "javac"), help="javac binary") + parser.add_argument("--max-patterns", type=int, default=5, help="max patterns to show per iteration") + args = parser.parse_args() + + log_path = args.log + if args.run: + log_path = args.output + run_plan_evolution( + log=log_path, + iterations=args.iterations, + themes=args.themes, + query_indexes=args.query_indexes, + classpath_file=args.classpath_file, + java_bin=args.java, + javac_bin=args.javac, + ) + if log_path is None: + raise SystemExit("log path required (or use --run)") + + text = log_path.read_text() + data = parse_log(text) + + pairs = {} + for (theme, qidx, dp), iters in data.items(): + pairs.setdefault((theme, qidx), {})[dp] = iters + + for (theme, qidx), dp_iters in sorted(pairs.items()): + if "false" not in dp_iters or "true" not in dp_iters: + continue + false_iters = dp_iters["false"] + true_iters = dp_iters["true"] + all_iters = sorted(set(false_iters.keys()) | set(true_iters.keys())) + print(f"=== {theme} #{qidx} ===") + for it in all_iters: + f_block = false_iters.get(it, "") + t_block = true_iters.get(it, "") + f_patterns = extract_patterns(f_block, args.max_patterns) + t_patterns = extract_patterns(t_block, args.max_patterns) + print(f"iter {it} | dp=false: " + " -> ".join(f_patterns)) + print(f"iter {it} | dp=true : " + " -> ".join(t_patterns)) + print(f"iter {it} | diff : {diff_patterns(f_patterns, t_patterns)}") + print() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/site/content/documentation/programming/lmdb-store.md b/site/content/documentation/programming/lmdb-store.md index ced2e80d236..bd9001e2d17 100644 --- a/site/content/documentation/programming/lmdb-store.md +++ b/site/content/documentation/programming/lmdb-store.md @@ -116,6 +116,47 @@ config.setTripleDBSize(1_073_741_824L); Repository repo = new SailRepository(new LmdbStore(dataDir), config); ``` +### Learned join order optimization (experimental) + +The LMDB store can use the learned join optimizer (experimental). It records join fanout statistics in memory and uses them to reorder joins on subsequent query executions. By default, statistics are invalidated after 100,000 statement additions within 10 minutes, or when the default cardinality estimate for a pattern drifts by 50% or more. + +To enable or override the evaluation strategy factory, set it before the repository is initialized: + +```java +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +... +LmdbStore store = new LmdbStore(dataDir); +store.setEvaluationStrategyFactory(new LearningEvaluationStrategyFactory()); +Repository repo = new SailRepository(store); +repo.init(); +``` + +To configure or disable invalidation of learned stats based on write volume, pass a configured `MemoryJoinStats`: + +```java +import java.time.Duration; +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.query.algebra.evaluation.optimizer.MemoryJoinStats; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.lmdb.LmdbStore; +... +MemoryJoinStats.InvalidationSettings settings = + MemoryJoinStats.InvalidationSettings.of(Duration.ofMinutes(10), 100_000, 0.5d); +LearningEvaluationStrategyFactory factory = new LearningEvaluationStrategyFactory(new MemoryJoinStats(settings)); +LmdbStore store = new LmdbStore(dataDir); +store.setEvaluationStrategyFactory(factory); +Repository repo = new SailRepository(store); +repo.init(); +``` + +Use `MemoryJoinStats.InvalidationSettings.disabled()` to keep stats indefinitely. + +To disable the learned optimizer, replace the factory with `DefaultEvaluationStrategyFactory` (or the deprecated `StrictEvaluationStrategyFactory`, which is the LMDB default). + ## Required storage space, RAM size and disk performance You can expect a footprint of around 120 - 130 bytes per quad when using the LMDB store with 3 indexes (like spoc, ospc and psoc). diff --git a/site/content/documentation/programming/repository.md b/site/content/documentation/programming/repository.md index 52cca3cd8c1..e6bce8f1b2f 100644 --- a/site/content/documentation/programming/repository.md +++ b/site/content/documentation/programming/repository.md @@ -102,6 +102,28 @@ In the unlikely event of corruption the system property `org.eclipse.rdf4j.sail. allow the NativeStore to output CorruptValue/CorruptIRI/CorruptIRIOrBNode/CorruptLiteral objects. Take a backup of all data before setting this property as it allows the NativeStore to delete corrupt indexes in an attempt to recreate them. Consider this feature experimental and use with caution. +#### Learned join order optimization (experimental) + +For workloads with repeated or similar queries, the learned join optimizer records join fanout at runtime and uses it to reorder joins on subsequent executions. Statistics are kept in memory and reset on restart. + +NativeStore enables the learned optimizer by default. MemoryStore and LmdbStore default to the standard/strict evaluation strategies, so enable the learned optimizer explicitly when needed. To override or disable it, configure the evaluation strategy factory before initializing the repository: + +```java +import org.eclipse.rdf4j.query.algebra.evaluation.impl.LearningEvaluationStrategyFactory; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.nativerdf.NativeStore; +... +NativeStore store = new NativeStore(dataDir); +store.setEvaluationStrategyFactory(new LearningEvaluationStrategyFactory()); +Repository repo = new SailRepository(store); +repo.init(); +``` + +By default, statistics are invalidated after 100,000 statement additions within 10 minutes, or when the default cardinality estimate for a pattern drifts by 50% or more. You can customize this by supplying a configured `MemoryJoinStats` to the factory (including `MemoryJoinStats.InvalidationSettings.of(window, threshold, baselineDriftRatio)`), or disable it entirely via `MemoryJoinStats.InvalidationSettings.disabled()`. + +To disable the learned optimizer, replace the factory with the default `DefaultEvaluationStrategyFactory`. + ### Elasticsearch RDF Repository {{< tag " New in RDF4J 3.1" >}} diff --git a/testsuites/benchmark-common/src/main/java/org/eclipse/rdf4j/benchmark/rio/util/ThemeDataSetGenerator.java b/testsuites/benchmark-common/src/main/java/org/eclipse/rdf4j/benchmark/rio/util/ThemeDataSetGenerator.java index 46bc88d4ab2..cd3b5160469 100644 --- a/testsuites/benchmark-common/src/main/java/org/eclipse/rdf4j/benchmark/rio/util/ThemeDataSetGenerator.java +++ b/testsuites/benchmark-common/src/main/java/org/eclipse/rdf4j/benchmark/rio/util/ThemeDataSetGenerator.java @@ -1054,7 +1054,7 @@ private static IRI iri(String namespace, String localName) { } private static IRI entity(String namespace, String category, int id) { - return VF.createIRI(namespace, category + "/" + id); + return VF.createIRI(namespace + category + "/" + id); } private static Literal literal(String value) {