blob: 120eb5e804a3774c92cfde5ec159b4d1ffd8c165 [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.tdb.solver;
import static org.apache.jena.atlas.lib.tuple.TupleFactory.tuple ;
import java.util.* ;
import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.jena.atlas.iterator.Iter ;
import org.apache.jena.atlas.iterator.IteratorWrapper ;
import org.apache.jena.atlas.lib.tuple.Tuple ;
import org.apache.jena.atlas.lib.tuple.TupleFactory ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.query.QueryCancelledException ;
import org.apache.jena.sparql.core.BasicPattern ;
import org.apache.jena.sparql.core.Quad ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.engine.ExecutionContext ;
import org.apache.jena.sparql.engine.QueryIterator ;
import org.apache.jena.sparql.engine.binding.Binding ;
import org.apache.jena.sparql.engine.binding.BindingFactory ;
import org.apache.jena.sparql.engine.binding.BindingMap ;
import org.apache.jena.sparql.engine.iterator.QueryIterNullIterator ;
import org.apache.jena.tdb.TDBException ;
import org.apache.jena.tdb.lib.NodeLib ;
import org.apache.jena.tdb.store.DatasetGraphTDB ;
import org.apache.jena.tdb.store.GraphTDB ;
import org.apache.jena.tdb.store.NodeId ;
import org.apache.jena.tdb.store.nodetable.NodeTable ;
import org.apache.jena.tdb.store.nodetupletable.NodeTupleTable ;
import org.apache.jena.tdb.sys.TDBInternal ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
/** Utilities used within the TDB BGP solver : local TDB store */
public class SolverLib
{
private static Logger log = LoggerFactory.getLogger(SolverLib.class) ;
/** Non-reordering execution of a basic graph pattern, given a iterator of bindings as input */
public static QueryIterator execute(GraphTDB graph, BasicPattern pattern,
QueryIterator input, Predicate<Tuple<NodeId>> filter,
ExecutionContext execCxt)
{
// Maybe default graph or named graph.
NodeTupleTable ntt = graph.getNodeTupleTable() ;
return execute(ntt, graph.getGraphName(), pattern, input, filter, execCxt) ;
}
/** Non-reordering execution of a quad pattern, given a iterator of bindings as input.
* GraphNode is Node.ANY for execution over the union of named graphs.
* GraphNode is null for execution over the real default graph.
*/
public static QueryIterator execute(DatasetGraphTDB ds, Node graphNode, BasicPattern pattern,
QueryIterator input, Predicate<Tuple<NodeId>> filter,
ExecutionContext execCxt)
{
NodeTupleTable ntt = ds.chooseNodeTupleTable(graphNode) ;
return execute(ntt, graphNode, pattern, input, filter, execCxt) ;
}
public static Iterator<BindingNodeId> convertToIds(Iterator<Binding> iterBindings, NodeTable nodeTable)
{ return Iter.map(iterBindings, convFromBinding(nodeTable)) ; }
/** Convert from Iterator<BindingNodeId> to Iterator<Binding>, conversion "on demand"
* (in convToBinding(BindingNodeId, NodeTable)
*/
public static Iterator<Binding> convertToNodes(Iterator<BindingNodeId> iterBindingIds, NodeTable nodeTable)
{ return Iter.map(iterBindingIds, bindingNodeIds -> convToBinding(bindingNodeIds, nodeTable)) ; }
// The worker. Callers choose the NodeTupleTable.
// graphNode may be Node.ANY, meaning we should make triples unique.
// graphNode may be null, meaning default graph
private static QueryIterator execute(NodeTupleTable nodeTupleTable, Node graphNode, BasicPattern pattern,
QueryIterator input, Predicate<Tuple<NodeId>> filter,
ExecutionContext execCxt)
{
if ( Quad.isUnionGraph(graphNode) )
graphNode = Node.ANY ;
if ( Quad.isDefaultGraph(graphNode) )
graphNode = null ;
List<Triple> triples = pattern.getList() ;
boolean anyGraph = (graphNode==null ? false : (Node.ANY.equals(graphNode))) ;
int tupleLen = nodeTupleTable.getTupleTable().getTupleLen() ;
if ( graphNode == null ) {
if ( 3 != tupleLen )
throw new TDBException("SolverLib: Null graph node but tuples are of length "+tupleLen) ;
} else {
if ( 4 != tupleLen )
throw new TDBException("SolverLib: Graph node specified but tuples are of length "+tupleLen) ;
}
// Convert from a QueryIterator (Bindings of Var/Node) to BindingNodeId
NodeTable nodeTable = nodeTupleTable.getNodeTable() ;
Iterator<BindingNodeId> chain = Iter.map(input, SolverLib.convFromBinding(nodeTable)) ;
List<Abortable> killList = new ArrayList<>() ;
for ( Triple triple : triples )
{
Tuple<Node> tuple = null ;
if ( graphNode == null )
// 3-tuples
tuple = tuple(triple.getSubject(), triple.getPredicate(), triple.getObject()) ;
else
// 4-tuples.
tuple = tuple(graphNode, triple.getSubject(), triple.getPredicate(), triple.getObject()) ;
chain = solve(nodeTupleTable, tuple, anyGraph, chain, filter, execCxt) ;
chain = makeAbortable(chain, killList) ;
}
// DEBUG POINT
if ( false )
{
if ( chain.hasNext())
chain = Iter.debug(chain) ;
else
System.out.println("No results") ;
}
// Timeout wrapper ****
// QueryIterTDB gets called async.
// Iter.abortable?
// Or each iterator has a place to test.
// or pass in a thing to test?
// Need to make sure the bindings here point to parent.
Iterator<Binding> iterBinding = convertToNodes(chain, nodeTable) ;
// "input" will be closed by QueryIterTDB but is otherwise unused.
// "killList" will be aborted on timeout.
return new QueryIterTDB(iterBinding, killList, input, execCxt) ;
}
/** Create an abortable iterator, storing it in the killList.
* Just return the input iterator if kilList is null.
*/
static <T> Iterator<T> makeAbortable(Iterator<T> iter, List<Abortable> killList)
{
if ( killList == null )
return iter ;
IterAbortable<T> k = new IterAbortable<>(iter) ;
killList.add(k) ;
return k ;
}
/** Iterator that adds an abort operation which can be called
* at any time, including from another thread, and causes the
* iterator to throw an exception when next touched (hasNext, next).
*/
static class IterAbortable<T> extends IteratorWrapper<T> implements Abortable
{
volatile boolean abortFlag = false ;
public IterAbortable(Iterator<T> iterator)
{
super(iterator) ;
}
/** Can call asynchronously at anytime */
@Override
public void abort() {
abortFlag = true ;
}
@Override
public boolean hasNext()
{
if ( abortFlag )
throw new QueryCancelledException() ;
return iterator.hasNext() ;
}
@Override
public T next()
{
if ( abortFlag )
throw new QueryCancelledException() ;
return iterator.next() ;
}
}
public static Iterator<BindingNodeId> solve(NodeTupleTable nodeTupleTable,
Tuple<Node> tuple,
boolean anyGraph,
Iterator<BindingNodeId> chain, Predicate<Tuple<NodeId>> filter,
ExecutionContext execCxt)
{
return new StageMatchTuple(nodeTupleTable, chain, tuple, anyGraph, filter, execCxt) ;
}
public static Binding convToBinding(BindingNodeId bindingNodeIds, NodeTable nodeTable) {
if ( true )
return new BindingTDB(bindingNodeIds, nodeTable) ;
else {
// Makes nodes immediately. Causing unnecessary NodeTable accesses
// (e.g. project)
BindingMap b = BindingFactory.create() ;
for (Var v : bindingNodeIds) {
NodeId id = bindingNodeIds.get(v) ;
Node n = nodeTable.getNodeForNodeId(id) ;
b.add(v, n) ;
}
return b ;
}
}
// Transform : Binding ==> BindingNodeId
public static Function<Binding, BindingNodeId> convFromBinding(final NodeTable nodeTable)
{
return binding -> SolverLib.convert(binding, nodeTable);
}
/** Binding {@literal ->} BindingNodeId, given a NodeTable */
public static BindingNodeId convert(Binding binding, NodeTable nodeTable)
{
if ( binding instanceof BindingTDB )
return ((BindingTDB)binding).getBindingId() ;
BindingNodeId b = new BindingNodeId(binding) ;
// and copy over, getting NodeIds.
Iterator<Var> vars = binding.vars() ;
for ( ; vars.hasNext() ; )
{
Var v = vars.next() ;
Node n = binding.get(v) ;
if ( n == null )
// Variable mentioned in the binding but not actually defined.
// Can occur with BindingProject
continue ;
// Rely on the node table cache for efficency - we will likely be
// repeatedly looking up the same node in different bindings.
NodeId id = nodeTable.getNodeIdForNode(n) ;
// Optional: whether to put in "known missing"
// Currently, we do. The rest of the code should work with either choice.
//if ( ! NodeId.isDoesNotExist(id) )
b.put(v, id) ;
}
return b ;
}
/** Find whether a specific graph name is in the quads table. */
public static QueryIterator testForGraphName(DatasetGraphTDB ds, Node graphNode, QueryIterator input,
Predicate<Tuple<NodeId>> filter, ExecutionContext execCxt) {
NodeId nid = TDBInternal.getNodeId(ds, graphNode) ;
boolean exists = !NodeId.isDoesNotExist(nid) ;
if ( exists ) {
// Node exists but is it used in the quad position?
NodeTupleTable ntt = ds.getQuadTable().getNodeTupleTable() ;
// Don't worry about abortable - this iterator should be fast
// (with normal indexing - at least one G???).
// Either it finds a starting point, or it doesn't. We are only
// interested in the first .hasNext.
Iterator<Tuple<NodeId>> iter1 = ntt.find(nid, NodeId.NodeIdAny, NodeId.NodeIdAny, NodeId.NodeIdAny) ;
if ( filter != null )
iter1 = Iter.filter(iter1, filter) ;
exists = iter1.hasNext() ;
}
if ( exists )
return input ;
else {
input.close() ;
return QueryIterNullIterator.create(execCxt) ;
}
}
/** Find all the graph names in the quads table. */
public static QueryIterator graphNames(DatasetGraphTDB ds, Node graphNode, QueryIterator input,
Predicate<Tuple<NodeId>> filter, ExecutionContext execCxt) {
List<Abortable> killList = new ArrayList<>() ;
Iterator<Tuple<NodeId>> iter1 = ds.getQuadTable().getNodeTupleTable().find(NodeId.NodeIdAny, NodeId.NodeIdAny,
NodeId.NodeIdAny, NodeId.NodeIdAny) ;
if ( filter != null )
iter1 = Iter.filter(iter1, filter) ;
Iterator<NodeId> iter2 = Iter.map(iter1, (t) -> t.get(0)) ;
iter2 = makeAbortable(iter2, killList) ;
Iterator<NodeId> iter3 = Iter.distinct(iter2) ;
iter3 = makeAbortable(iter3, killList) ;
Iterator<Node> iter4 = NodeLib.nodes(ds.getQuadTable().getNodeTupleTable().getNodeTable(), iter3) ;
final Var var = Var.alloc(graphNode) ;
Iterator<Binding> iterBinding = Iter.map(iter4, node -> BindingFactory.binding(var, node)) ;
// Not abortable.
return new QueryIterTDB(iterBinding, killList, input, execCxt) ;
}
public static Set<NodeId> convertToNodeIds(Collection<Node> nodes, DatasetGraphTDB dataset)
{
Set<NodeId> graphIds = new HashSet<>() ;
NodeTable nt = dataset.getQuadTable().getNodeTupleTable().getNodeTable() ;
for ( Node n : nodes )
graphIds.add(nt.getNodeIdForNode(n)) ;
return graphIds ;
}
public static Iterator<Tuple<NodeId>> unionGraph(NodeTupleTable ntt)
{
Iterator<Tuple<NodeId>> iter = ntt.find((NodeId)null, null, null, null) ;
iter = Iter.map(iter, quadsToAnyTriples) ;
//iterMatches = Iter.distinct(iterMatches) ;
// This depends on the way indexes are choose and
// the indexing pattern. It assumes that the index
// chosen ends in G so same triples are adjacent
// in a union query.
/// See TupleTable.scanAllIndex that ensures this.
iter = Iter.distinctAdjacent(iter) ;
return iter ;
}
// -- Mutating "transform in place"
private static Function<Tuple<NodeId>, Tuple<NodeId>> quadsToAnyTriples = item -> {
return TupleFactory.create4(NodeId.NodeIdAny, item.get(1), item.get(2), item.get(3) ) ;
} ;
}