private TupleExpr optimize(TupleExpr parsed, Dataset dataset, BindingSet bindings, boolean includeInferred,
EvaluationStrategy strategy) throws SailException {
LOGGER.trace("Incoming query model:\n{}", parsed);
// Clone the tuple expression to allow for more aggressive optimisations
TupleExpr query = new QueryRoot(parsed.clone());
new BindingAssigner().optimize(query, dataset, bindings);
new ConstantOptimizer(strategy).optimize(query, dataset, bindings);
new CompareOptimizer().optimize(query, dataset, bindings);
new ConjunctiveConstraintSplitter().optimize(query, dataset, bindings);
new DisjunctiveConstraintOptimizer().optimize(query, dataset, bindings);
new SameTermFilterOptimizer().optimize(query, dataset, bindings);
new QueryModelPruner().optimize(query, dataset, bindings);
new QueryMultiJoinOptimizer().optimize(query, dataset, bindings);
// new FilterOptimizer().optimize(query, dataset, bindings);
// prepare bloom filters
RepositoryBloomFilter defaultBloomFilter = new AccurateRepositoryBloomFilter(includeInferred);
Map<Repository, RepositoryBloomFilter> bloomFilters = federation.getBloomFilters();
java.util.function.Function<Repository, RepositoryBloomFilter> bloomFilterFunction = c -> bloomFilters
.getOrDefault(c, defaultBloomFilter);
new EmptyPatternOptimizer(members, bloomFilterFunction).optimize(query, dataset, bindings);
boolean distinct = federation.isDistinct();
PrefixHashSet local = federation.getLocalPropertySpace();
new FederationJoinOptimizer(members, distinct, local, bloomFilterFunction).optimize(query, dataset, bindings);
new OwnedTupleExprPruner().optimize(query, dataset, bindings);
new QueryModelPruner().optimize(query, dataset, bindings);
new QueryMultiJoinOptimizer().optimize(query, dataset, bindings);
new PrepareOwnedTupleExpr().optimize(query, dataset, bindings);
LOGGER.trace("Optimized query model:\n{}", query);
return query;
}
@Override
public Iterable<QueryOptimizer> getOptimizers() {
return Arrays.asList(
new BindingAssigner(),
new ConstantOptimizer(strategy),
new CompareOptimizer(),
new ConjunctiveConstraintSplitter(),
new DisjunctiveConstraintOptimizer(),
new SameTermFilterOptimizer(),
new QueryModelNormalizer(),
new HalyardQueryJoinOptimizer(statistics),
new IterativeEvaluationOptimizer(),
new HalyardFilterOptimizer(),
new OrderLimitOptimizer());
}
@Override
public TupleExpr optimize(TupleExpr expr, EvaluationStatistics evaluationStatistics,
BindingSet bindings) {
if (!(evaluationStatistics instanceof FederationEvaluationStatistics)) {
throw new FedXRuntimeException(
"Expected FederationEvaluationStatistics, was " + evaluationStatistics.getClass());
}
FederationEvaluationStatistics stats = (FederationEvaluationStatistics) evaluationStatistics;
QueryInfo queryInfo = stats.getQueryInfo();
Dataset dataset = stats.getDataset();
FederationContext federationContext = queryInfo.getFederationContext();
List<Endpoint> members;
if (dataset instanceof FedXDataset) {
// run the query against a selected set of endpoints
FedXDataset ds = (FedXDataset) dataset;
members = federationContext.getEndpointManager().getEndpoints(ds.getEndpoints());
} else {
// evaluate against entire federation
FedX fed = federationContext.getFederation();
members = fed.getMembers();
}
// Clone the tuple expression to allow for more aggressive optimizations
TupleExpr query = new QueryRoot(expr.clone());
GenericInfoOptimizer info = new GenericInfoOptimizer(queryInfo);
// collect information and perform generic optimizations
info.optimize(query);
// if the federation has a single member only, evaluate the entire query there
if (members.size() == 1 && queryInfo.getQuery() != null && propagateServices(info.getServices())
&& queryInfo.getQueryType() != QueryType.UPDATE) {
return new SingleSourceQuery(expr, members.get(0), queryInfo);
}
if (log.isTraceEnabled()) {
log.trace("Query before Optimization: " + query);
}
/* original RDF4J optimizers */
new ConstantOptimizer(this).optimize(query, dataset, bindings); // maybe remove this optimizer later
new DisjunctiveConstraintOptimizer().optimize(query, dataset, bindings);
/*
* TODO add some generic optimizers: - FILTER ?s=1 && ?s=2 => EmptyResult - Remove variables that are not
* occurring in query stmts from filters
*/
/* custom optimizers, execute only when needed */
// if the query has a single relevant source (and if it is not a SERVICE query), evaluate at this source only
// Note: UPDATE queries are always handled in the federation engine to adhere to the configured
// write strategy
Set<Endpoint> relevantSources = performSourceSelection(members, cache, queryInfo, info);
if (relevantSources.size() == 1 && propagateServices(info.getServices())
&& queryInfo.getQueryType() != QueryType.UPDATE) {
return new SingleSourceQuery(query, relevantSources.iterator().next(), queryInfo);
}
if (info.hasService()) {
new ServiceOptimizer(queryInfo).optimize(query);
}
// optimize unions, if available
if (info.hasUnion()) {
new UnionOptimizer(queryInfo).optimize(query);
}
optimizeExclusiveExpressions(query, queryInfo, info);
// optimize statement groups and join order
optimizeJoinOrder(query, queryInfo, info);
// potentially push limits (if applicable)
if (info.hasLimit()) {
new LimitOptimizer().optimize(query);
}
// optimize Filters, if available
// Note: this is done after the join order is determined to ease filter pushing
if (info.hasFilter()) {
new FilterOptimizer().optimize(query);
}
if (log.isTraceEnabled()) {
log.trace("Query after Optimization: " + query);
}
return query;
}