blob: d05f361dfc941e12f18a80a10c3034ab3fbfbd80 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.sparql.engine.optimizer.reorder;
import static org.apache.jena.sparql.util.StringUtils.printAbbrev ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.stream.Collectors;
import org.apache.jena.atlas.iterator.Iter ;
import org.apache.jena.atlas.lib.StrUtils ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.sparql.ARQException ;
import org.apache.jena.sparql.core.BasicPattern ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.sse.Item ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
/** Machinary.
* This code implements the connectiveness assumed by execution based on substitution (index joins).
* i.e. if <code>{ ?x :p ?v . ?x :q ?w }</code> then <code>?x</code> is <code>TERM</code>
* at the second triple.
*/
public abstract class ReorderTransformationSubstitution implements ReorderTransformation
{
static public final Logger log = LoggerFactory.getLogger(ReorderTransformationSubstitution.class) ;
static private final boolean DEBUG = false ;
public ReorderTransformationSubstitution() {}
@Override
public BasicPattern reorder(BasicPattern pattern) {
return reorderIndexes(pattern).reorder(pattern) ;
}
@Override
public final ReorderProc reorderIndexes(BasicPattern pattern) {
if ( pattern.size() <= 1 )
return ReorderLib.identityProc() ;
List<Triple> triples = pattern.getList() ;
// Could merge into the conversion step to do the rewrite WRT a Binding.
// Or done here as a second pass mutate of PatternTriples
// Convert to a mutable form (that allows things like "TERM")
List<PatternTriple> components = Iter.toList(Iter.map(triples.iterator(), PatternTriple::new)) ;
// Allow subclasses to get in (e.g. static reordering).
components = modifyComponents(components) ;
ReorderProc proc = reorder(triples, components) ;
return proc ;
}
protected List<PatternTriple> modifyComponents(List<PatternTriple> components) {
return components ;
}
private String formatted(List<PatternTriple> components) {
return components.stream().map(c->"(" + printAbbrev(c.toString()) + ")").collect(Collectors.joining(" "));
}
protected ReorderProc reorder(List<Triple> triples, List<PatternTriple> components) {
int N = components.size() ;
int numReorder = N ; // Maybe choose 4, say, and copy over the rest.
int indexes[] = new int[N] ;
if ( DEBUG )
log.debug("Reorder: " + formatted(components));
int idx = 0 ;
for ( ; idx < numReorder ; idx++ ) {
int j = chooseNext(components) ;
if ( j < 0 )
break ;
Triple triple = triples.get(j) ;
indexes[idx] = j ;
update(triple, components) ;
components.set(j, null) ;
}
// Copy over the remainder (if any)
for ( int i = 0 ; i < components.size() ; i++ ) {
if ( components.get(i) != null )
indexes[idx++] = i ;
}
if ( triples.size() != idx )
throw new ARQException(String.format("Inconsistency: number of triples (%d) does not equal to number of indexes processed (%d)",
triples.size(), idx)) ;
ReorderProc proc = new ReorderProcIndexes(indexes) ;
return proc ;
}
/** Return index of next pattern triple */
protected int chooseNext(List<PatternTriple> pTriples) {
if ( DEBUG ) {
int i = -1 ;
StringBuilder buff = new StringBuilder() ;
for ( PatternTriple pt : pTriples ) {
i++ ;
if ( pt == null ) {
buff.append(String.format(" %d : null\n", i)) ;
continue ;
}
double w2 = weight(pt) ;
buff.append(String.format(" %d %8.0f : %s\n", i, w2, printAbbrev(pt))) ;
}
String x = StrUtils.noNewlineEnding(buff.toString()) ;
log.debug(">> Input\n" + x) ;
}
int idx = processPTriples(pTriples, null) ;
if ( DEBUG ) {
String x = printAbbrev(pTriples.get(idx)) ;
x = StrUtils.noNewlineEnding(x) ;
log.debug("<< Output\n " + x) ;
}
return idx ;
}
/** Return all the indexes of pattern triples of the least weight. */
protected List<Integer> chooseAll(List<PatternTriple> pTriples) {
List<Integer> results = new ArrayList<>(pTriples.size()) ;
processPTriples(pTriples, results) ;
return results ;
}
/**
* Return the index of the first, least triple; optionally accumulate all indexes of
* the same least weight
*/
private int processPTriples(List<PatternTriple> pTriples, List<Integer> results) {
double min = Double.MAX_VALUE ; // Current minimum
int N = pTriples.size() ;
int idx = -1 ;
for ( int i = 0 ; i < N ; i++ ) {
PatternTriple pt = pTriples.get(i) ;
if ( pt == null )
continue ;
double x = weight(pt) ;
if ( x < 0 ) {
// ****
DefaultChoice choice = defaultChoice(pt) ;
if ( choice != null ) {
switch (choice) {
case FIRST :
// Weight very low so it goes to front.
x = 0.01 ;
break ;
case LAST :
// Default action :
break ;
case ZERO :
x = 0 ;
break ;
case NUMERIC :
x = defaultWeight(pt) ;
break ;
}
}
// Not found. No default action.
// Make sure something is returned but otherwise ignore this pattern (goes
// last).
if ( idx == -1 ) {
idx = i ;
if ( results != null )
results.add(i) ;
}
// Do nothing. Does not update min so will be not be in results.
continue ;
}
if ( x == min ) {
if ( results != null )
results.add(i) ;
continue ;
}
if ( x < min ) {
min = x ;
idx = i ;
if ( results != null ) {
results.clear() ;
results.add(i) ;
}
}
}
return idx ;
}
/** Return the weight of the pattern, or -1 if no knowledge for it */
protected abstract double weight(PatternTriple pt) ;
protected enum DefaultChoice { ZERO, LAST, FIRST , NUMERIC ; }
/** What to do if the {@link weight} comes back as "not found".
* Choices are:
* ZERO Assume the weight is zero (the rules were complete over the data so this is a pattern that will not match the data.
* LAST Place after all explicitly weighted triple patterns
* FIRST Place before all explicitly weighted triple patterns
* NUMERIC Use value returned by {@link defaultWeight}
* The default, default choice is LAST.
*/
protected DefaultChoice defaultChoice(PatternTriple pt) { return null ; } // return DefaultChoice.LAST ; }
protected double defaultWeight(PatternTriple pt) { return -1 ; }
/** Update components to note any variables from triple */
protected static void update(Triple triple, List<PatternTriple> components) {
for ( PatternTriple elt : components )
if ( elt != null )
update(triple, elt) ;
}
private static void update(Triple triple, PatternTriple tuple) {
update(triple.getSubject(), tuple) ;
update(triple.getPredicate(), tuple) ;
update(triple.getObject(), tuple) ;
}
private static void update(Node node, PatternTriple elt) {
if ( Var.isVar(node) ) {
if ( node.equals(elt.subject.getNode()) )
elt.subject = PatternElements.TERM ;
if ( node.equals(elt.predicate.getNode()) )
elt.predicate = PatternElements.TERM ;
if ( node.equals(elt.object.getNode()) )
elt.object = PatternElements.TERM ;
}
}
/** Update based on a variable/value (c.f. Substitute.substitute) */
protected static void update(Var var, Node value, List<PatternTriple> components) {
for ( PatternTriple elt : components )
if ( elt != null )
update(var, value, elt) ;
}
private static void update(Var var, Node value, PatternTriple elt) {
if ( var.equals(elt.subject.getNode()) )
elt.subject = Item.createNode(value) ;
if ( var.equals(elt.predicate.getNode()) )
elt.predicate = Item.createNode(value) ;
if ( var.equals(elt.object.getNode()) )
elt.object = Item.createNode(value) ;
}
}