/*
* 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.iterator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.jena.atlas.io.IndentedWriter;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.query.QueryCancelledException;
import org.apache.jena.query.SortCondition;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.engine.ExecutionContext;
import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.engine.binding.BindingComparator;
import org.apache.jena.sparql.engine.binding.BindingFactory;
import org.apache.jena.sparql.engine.binding.BindingMap;
import org.apache.jena.sparql.engine.iterator.QueryIterSort;
import org.apache.jena.sparql.engine.iterator.QueryIteratorBase;
import org.apache.jena.sparql.engine.main.OpExecutor;
import org.apache.jena.sparql.engine.main.OpExecutorFactory;
import org.apache.jena.sparql.graph.GraphMemPlain;
import org.apache.jena.sparql.serializer.SerializationContext;
import org.apache.jena.sparql.util.Context;
import org.junit.Test;
import junit.framework.TestCase;
/*
Test that a SortedDataBag used inside a QueryIterSort
does indeed cut off when cancelled.
This is horribly clunky because of the effort of
setting up. Maybe we should instead be content to
test the SortedDataBag correctly?
*/
public class TestSortedDataBagCancellation extends TestCase {
static final BindingMap b1 = BindingFactory.create();
static final BindingMap b2 = BindingFactory.create();
static final BindingMap b3 = BindingFactory.create();
static final BindingMap b4 = BindingFactory.create();
static {
b1.add(Var.alloc("v1"), NodeFactory.createLiteral("alpha"));
b2.add(Var.alloc("v2"), NodeFactory.createLiteral("beta"));
b3.add(Var.alloc("v3"), NodeFactory.createLiteral("gamma"));
b4.add(Var.alloc("v4"), NodeFactory.createLiteral("delta"));
}
final Context params = new Context();
final OpExecutorFactory factory = new OpExecutorFactory() {
@Override
public OpExecutor create(ExecutionContext ec) {
throw new UnsupportedOperationException();
}
};
final Graph activeGraph = new GraphMemPlain();
final DatasetGraph dataset = DatasetGraphFactory.create();
final List<SortCondition> conditions = new ArrayList<SortCondition>();
final ExecutionContext ec = new ExecutionContext(params, activeGraph, dataset, factory);
final BindingComparator base_bc = new BindingComparator(conditions, ec);
final SpecialBindingComparator bc = new SpecialBindingComparator(base_bc, ec);
QueryIteratorItems baseIter = new QueryIteratorItems();
{
baseIter.bindings.add(b1);
baseIter.bindings.add(b2);
baseIter.bindings.add(b3);
baseIter.bindings.add(b4);
;
}
QueryIterSort qs = new QueryIterSort(baseIter, bc, ec);
/**
* In this test, the iterator is not cancelled; all items should be
* delivered, and the compare count should be monotonic-nondecreasing.
*/
@Test
public void testIteratesToCompletion() {
int count = 0;
assertEquals(0, count = bc.count);
Set<Binding> results = new HashSet<Binding>();
assertTrue(qs.hasNext());
assertTrue(bc.count >= count);
count = bc.count;
results.add(qs.next());
assertTrue(qs.hasNext());
assertTrue(bc.count >= count);
count = bc.count;
results.add(qs.next());
assertTrue(qs.hasNext());
assertTrue(bc.count >= count);
count = bc.count;
results.add(qs.next());
assertTrue(qs.hasNext());
assertTrue(bc.count >= count);
count = bc.count;
results.add(qs.next());
assertFalse(qs.hasNext());
Set<Binding> expected = new HashSet<Binding>();
expected.add(b1);
expected.add(b2);
expected.add(b3);
expected.add(b4);
assertEquals(expected, results);
}
/**
* In this test, the iterator is cancelled after the first result is
* delivered. Any attempt to run the comparator should be trapped an
* exception thrown. The iterators should deliver no more values.
*/
@Test
public void testIteratesWithCancellation() {
int count = 0;
assertEquals(0, count = bc.count);
Set<Binding> results = new HashSet<Binding>();
assertTrue(qs.hasNext());
assertTrue(bc.count >= count);
count = bc.count;
results.add(qs.next());
qs.cancel();
try {
bc.noMoreCalls();
while (qs.hasNext())
qs.next();
} catch (QueryCancelledException qe) {
assertTrue(qs.db.isCancelled());
return;
}
fail("query was not cancelled");
}
/**
* A QueryIterator that delivers the elements of a list of bindings.
*/
private static final class QueryIteratorItems extends QueryIteratorBase {
List<Binding> bindings = new ArrayList<Binding>();
int index = 0;
@Override
public void output(IndentedWriter out, SerializationContext sCxt) {
out.write("a QueryIteratorItems");
}
@Override
protected boolean hasNextBinding() {
return index < bindings.size();
}
@Override
protected Binding moveToNextBinding() {
index += 1;
return bindings.get(index - 1);
}
@Override
protected void closeIterator() {
}
@Override
protected void requestCancel() {
}
}
/**
* A BindingComparator that wraps another BindingComparator and counts how
* many times compare() is called.
*/
static class SpecialBindingComparator extends BindingComparator {
final BindingComparator base;
int count = 0;
boolean trapCompare = false;
public SpecialBindingComparator(BindingComparator base, ExecutionContext ec) {
super(base.getConditions(), ec);
this.base = base;
}
public void noMoreCalls() {
trapCompare = true;
}
@Override
public int compare(Binding x, Binding y) {
if (trapCompare)
throw new RuntimeException("compare() no longer allowed.");
count += 1;
return base.compare(x, y);
}
}
}